Y: predictions for the RNG model and the tail turn record, written before the build
This commit is contained in:
parent
b48d860f8a
commit
20e5112398
1 changed files with 133 additions and 0 deletions
133
docs/Y-standalone-rng-and-tail.md
Normal file
133
docs/Y-standalone-rng-and-tail.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# 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.
|
||||
Loading…
Add table
Reference in a new issue