# Lane Y — the predictions, written before the build Committed before `src/app` was touched, per earned rule 2. Falsification sections included: how each prediction could be wrong, and the symptom of each way. Two things are being predicted: (a) what the standalone's generator does on lane Z's two calibrated oracle pairs, and (b) whether a turn-record model built from the wire reproduces the record the game already wrote into every save it made. --- ## P-Y1 — the trade-raid model costs exactly 16 words on an 8-player save Lane Z measured `ServerTradeManager::GenerateTradeRaidEncounters` firing two `Mars::RNG::Chance` rolls for every entry of the server's player vector, and showed that no back-edge contains either site, so one word per player per site is a **bound**, not an observation. Both probabilities are strictly inside (0, 1), so `Chance` takes neither early-out and spends exactly one word. The reference save's player vector holds **8** entries (`Sim.NumPlrs`; the wire's `players` array is that same vector). > **The standalone, run with `--commit-rng` on `z2-endturn.sav`, consumes exactly 16 generator > words**, all of them attributed to the trade-raid phase, and the post-turn `left` is > `395 − 16 = 379`. `mt[624]` is unchanged (no block boundary is crossed). Same claim on `z-t6-endturn.sav`: 16 words, `left` `375 − 16 = 359`. **Falsification.** | how it could be wrong | symptom | |---|---| | the loop is not over the server's player vector | a word count that is not 2 × 8 | | `Chance` is `p >= r` or narrows differently | word count still 16 (the count does not depend on the comparison) — this prediction cannot test it, and says so | | the NPC-raid gate is not open on this save | 8 words, not 16 | | the refugee-raid gate *is* open | 24 words | | the engine's `next_float` disagrees with the original | invisible here; only the count is being tested | --- ## P-Y2 — the residual against the oracle is 4 and 2, not 0 The oracle pairs carry a known cost: `z2-endturn` → `z2-autosave` is **20** words, `z-t6-endturn` → `z-t6-autosave` is **18**. Lane Z's per-site ledger attributes the difference to four sites the standalone does not model: * 0x0050329d (`NextFloat`) and 0x007929a4 (`NextInt`), both under `StrategyServer::DetectEncounters` — one word each per turn on 3 of 3 measured turns; * `TechTree::ProcessResearch`+0x1c8 and `ServerPlayer::RollResearchEvent`+0x2f — 0–1 words each, both downstream of the research allocation, which is downstream of `ComputeBudget`, which is blocked on the population → base-output term. > **The standalone lands 4 words short on the turn-5 pair and 2 words short on the turn-6 pair**, and > the shortfall is exactly the four sites above: 2 detection + 2 research on turn 5, 2 detection + 0 > research on turn 6. The generator state therefore does **not** match the oracle on either pair, and > the answer to "does it land on the oracle's `mt[624]`/`left`" is **no**, by a stated amount. **Falsification.** A residual other than 4 and 2 means the site ledger and the file oracle disagree about this turn, which would be a real contradiction between two of lane Z's three instruments and would have to be reported as one. A residual of 0 would mean the trade-raid count is coincidentally absorbing the other sites. **Why the shortfall is not closed by guessing.** Modelling "2 detection words per turn" would fit three observations with no derived rule behind it, and lane I bounds that site's cost at `0 .. |contacts| × |detectors|`. It is left unmodelled and reported. --- ## P-Y3 — six fields of the turn record are recoverable from the wire `StrategyServer::FinalizeTurnRecords` (tail phase 36) fills a per-player record and archives it by turn. The archive is on the wire: it is `/Sim/turnstats/nply[]/hist/stats[]`, one `stats` element per turn per player, and every save carries an element for its own `Frame`. > **A record built from a save's own state reproduces that save's archive entry for its own turn, on > every player of every save in the corpus, for these six fields:** > > | record field | rule | > |---|---| > | `trn` | the frame counter | > | `col` | the number of owned-system ids | > | `sav` | the player's savings | > | `inc` | savings minus previous-turn savings | > | `tch` | the number of tech-tree states in the completed state | > | `pop` | summed over owned systems: population plus the pending population bonus | > > and it does **not** reproduce `almem`, `tdinc`, `bat`, the acquired/lost system lists, or the > per-hull-class ship census, because each of those is written by a phase or an input the standalone > does not hold. **Provenance, stated plainly.** The six rules were found by search over the corpus in Python before this was written, so this is not a blind prediction of the rules. What it *is* a prediction of is that a C++ transcription over the engine's own typed shapes agrees with them on all 112 player-records — a test of the transcription and of the shape typing, not of the rules. Two of the six (`pop`, `inc`) were ambiguous in the Python search and were separated by the corpus: `pop` = Σ(`Pop` + `pbon`) matched 112/112 while Σ(`Pop` + `dcs`) matched 101/112. **Falsification.** Any player-record where the six computed fields differ from the stored ones. Particularly: a save where a player's owned-system list references a system id the save does not carry (the sum would silently drop a term). --- ## P-Y4 — committing phase 36 would regress, so it is not committed The record the standalone can build is the record for the **input** save's turn, not for the turn it is about to produce: `sav` after the turn needs `ApplyNetToSavings`, which is blocked, and `almem` needs the alliance-mask rebuild, which is a stub. > **Committing phase 36 would add a new archive element whose `sav`, `inc`, `almem`, `tdinc` and > class census are all wrong, turning one container-shaped divergence per player into several > leaf-shaped ones. The divergence report would show a large `regressed` count and a small `closed` > one.** It is therefore evaluated, reported, and not written. The divergence baseline stays > 5 closed / 0 regressed on both pairs. **Falsification.** If committing it closed more than it broke, the block was too conservative. That is measurable with `--commit-blocked` and is reported rather than argued. --- ## P-Y5 — the bankruptcy divisor in the engine is one ulp wrong `ComputeBankruptcyLimits` divides by `-0.15`. The constant the original loads is the double `-0.15000000596046448`, which is `(double)(float)-0.15f`. The two differ, and the difference is visible on a large enough maximum income. > **`ftol(maxIncome / -0.15)` and `ftol(maxIncome / -0.15000000596046448)` differ for some > `maxIncome` reachable in a real game**, and the smallest such value is small enough to be found by > a linear scan. This lane does not fix it: the file is another lane's. It is measured and reported. **Falsification.** If no `maxIncome` in the int32 range separates the two constants, the correction is real but inert and should be recorded as such. --- # Outcomes Written after the run, against the predictions above. Nothing above was edited. ## P-Y1 — held exactly 16 words on both calibrated pairs, all attributed to the trade-raid phase, `left` 379 and 359. The player vector read 8 on both, as the wire says. The falsification table's other branches did not fire: not 8 (the NPC gate was open), not 24 (the refugee gate was not). ## P-Y2 — held exactly Residual 4 on the turn-4→5 pair and 2 on the turn-5→6 pair, matching the per-call-site split (2 detection + 2 research, and 2 detection + 0 research). The state block is byte-identical in both cases and only `left` differs, so the two instruments disagree about nothing. **The answer to the milestone question is therefore "no", by 4 words and by 2.** One thing this pair of numbers does *not* establish, and the prediction should have said so: the residual is a subtraction, so it confirms the **sum** of the modelled and unmodelled terms, not each term separately. The static bound is what carries the 16; this arithmetic corroborates it. ## P-Y3 — held 480 fields over 80 player-records across 11 saves, 0 mismatches, 0 dangling owned-system ids. The same check runs inside every turn and is printed as a phase note. ## P-Y4 — held, and the shape of the result is more useful than the claim `--commit-blocked` closes 29 and regresses 17 on the first reference pair, and closes 13 and regresses 19 on the second. Every regressed leaf is in the unmodelled set: `almem` x8, `inc` x3, `sav` x3, three of the ship census. Nothing outside it moved. So "committing makes it worse" is **not** what the measurement says: on the first pair the turnstats subsystem is net +7 and on the second it is net -9. The record is right in six fields and wrong in five, and which way the net falls depends on the save. The phase stays blocked, and it becomes a clean +24 the moment the alliance mask and the budget land. ## P-Y5 — WRONG in its framing, and the correction is the result The prediction said the two constants differ "for some maxIncome reachable in a real game" and that the smallest such value could be found by a linear scan. The scan found it immediately, and the framing -- inherited from the phrase "one ulp out on large empires" -- was the wrong picture entirely. `trunc(m / -0.15)` and `trunc(m / -0.15000000596046448)` differ for **every m divisible by 3**: 1000 of the first 3000 integers, smallest `m = 3` giving -20 against -19. It is not precision loss at scale. `m / 0.15` is an exact integer whenever 3 divides m, and the two constants straddle it. And the multiples of 3 are only the floor: the disagreement rate is 33.3% up to about 1.2 million, 66.7% by 1.36 million and 100% at and above 3 million. Measured on the corpus by inverting the stored limit: **6 of 25 player-records would get a different stored value from the engine's current constant than the game wrote.** The defect is live, it is a whole unit rather than an ulp, and it is a third of all inputs at small values and all of them at large ones -- the opposite shape from "one ulp on large empires", which would have been harmless. The file belongs to another lane in flight, so it is reported and not fixed here. ## An unpredicted result: the blocked input is recoverable from every save The elimination limit is injective in the maximum-income sum it is computed from (slope about 6.67), so it inverts. Every save in the corpus therefore states, for every player that owns anything, the exact sum of its systems' maximum money outputs -- which is the term that blocks `ComputeBudget`, both savings phases, the bankruptcy limits and five fields of the turn record. That was not predicted and is the most useful thing this lane found. The same inversion recovers the protection-limit factor as 3.3 from the saves, independently of the game data files.