For as long as this fork has been live, two- or three-person shifts almost always rolled Survival: meteors, some atmo trouble, a calm shift; rarely a traitor and never the chaos preset. I assumed that was low-population math. It was a wire that was never connected.
How the roll actually works
SOLREIGN, like upstream, rolls round types from the weighted Secret table at round start. SecretRuleSystem.TryPickPreset selects a preset, checks its GameRule.minPlayers gate against the current count, discards a failed pick, and rerolls from what remains until one clears the bar. That avoids a 20-player preset stalling a three-player server.
The problem was the table and its actual gates. Upstream's Secret table in Resources/Prototypes/secret_weights.yml weights Nukeops 0.20, Traitor 0.55, Zombie 0.05, Survival 0.10, and Revolutionary 0.05. In roundstart.yml, Nukeops and Zombie need 20 players, Revolutionary 15, Traitor 5, and Survival nobody.
At 2–4 players, only Survival survives every reroll. It is deterministic, not merely weighted toward Survival: the other four entries are excluded before the dice matter.
That is why the symptom was so easy to misread. A two- or three-person crew should get calmer rounds more often than a crowded station, but it should still be a weighted choice. Here, the weights became irrelevant after the minimum-player gates eliminated four of five outcomes.
The reroll loop is doing its intended fail-safe work. It never selects a preset that cannot legally begin, and it never stalls a low-population server waiting for a 15- or 20-player condition. The error was supplying it a table whose legal choices did not match the population it was serving.
The part that actually stings
The tuned table already existed in Resources/Prototypes/_Solreign/GameRules/secret_solreign.yml: SolreignSecret. Built and play-tested for 3–8 players, it removes Nukeops, Zombie, and Revolutionary and uses Traitor 0.35; SolreignAntagsOnSpawn, the chaos preset with antags seated before spawn, 0.35 with a minimum of 3; Survival 0.15; Kessler Syndrome 0.10 with no antag minimum; and a 0.05 Changeling content-pack sliver after a few proving rounds.
Which table rolls is not decided by its presence in prototypes. The game.secret_weight_prototype CVar defaults to literal "Secret", so our tuned table was inert until config pointed to it by name. Nothing did. The station was running stock upstream odds since SolreignSecret was written.
The table itself was not broken. It was deliberately made for this population: dead-weight presets were removed, the options that can clear the bar were included, and Survival remained a legitimate calm option instead of the only outcome. I had built the remedy and failed to connect the server to it.
Its own header comment had described the config line needed to select it. That was not sufficient deployment evidence. A tuned table can be well designed, validated, and present in the right directory while the CVar still selects the upstream default.
Why nothing caught it
There was no exception. TryPickPreset logs an info line when it excludes options, then starts a legal round; the crew manifest, antag selection, and event scheduler all behave correctly for Survival. No crash, console red text, or assertion exposed the configuration mismatch.
This is the awkward class of deployment failure: every component can look healthy in isolation. The code selected a valid preset from the table it was told to use. Downstream systems received a valid Survival round. A prototype could validate and its tests could pass while the live server still read the upstream ID.
The release changes one config line under [game]: secret_weight_prototype = "SolreignSecret". No new code or migration. I pulled the live config afterward and confirmed it read back SolreignSecret, rather than assuming a prototype file had shipped itself.
There was nothing new to invent in the round system. The validation work was to confirm that the existing SolreignSecret table now sat on the route the server reads at round start, instead of remaining a tuned but unused alternative.
That readback is part of the fix, not an afterthought. A value in a repository and a value read by the live process are different evidence. Small shifts now roll the table that was written for them instead of a default that can only resolve to Survival at their population.
What it teaches
If you run an SS14 fork with a tuned weightedRandom table gated by minPlayers, deployment is not done when the prototype validates and tests go green. Confirm on the live config which CVar reads which table; a prototype is inert until something points a string at its ID. When a mode is always the same at one population, read the actual weights and gates together. The practical rule is simple: verify the live configuration value, then verify the table's eligible outcomes at the population you actually serve.
See you on shift.