The observed-design list is a MOVE-TO-BACK vector capped at 20 records per
DESIGN OWNER, and that is why lane CV's 55 odes leaves looked unrelated: a turn's
output is a permutation of its input, not an append.
rich turn ad-turn27 + BR's deep tcb vs bp-pinB-turn28 1092 -> 1058
36 closed / 2 regressed vs CV's run; 116 / 8 vs the 1166 baseline
canonical turn2-state + CB's tcb vs turn3-state 62 -> 61
1 closed / 0 regressed, set-differenced by leaf PATH
All five committed predictions held; P5 held in direction and was wrong in
magnitude (9 leaves, not 4 -- I predicted spurious records, not leaves).
CV's "no upstream dependency" is right for the design half and wrong for the
other 24 leaves of the 79, two separate ways, and both are named rather than
attempted: the otch/owep arms need three undecoded design->tech/weapon set
builders (the wire's own DOpts list covers 13 of the 18 tech names the turn
moves), and the design half is itself capped by SHIP CONSTRUCTION -- two designs
get their first ships that turn. Fed the true post-turn ship list the same code
leaves 1 leaf of 55 instead of 21, so the mechanism is not the residual.
Control, run: with the NPC-owner guard compiled out the canonical pair goes
61 -> 70 and the rich turn stays at 1058. The guard is load-bearing on one pair
and inert on the other, which is why both were run.
Coverage, stated as loudly as the divergence: 8 players swept, TWO produce any
record; the canonical pair offers 16 (player, ship) pairs of which the guard
drops 15, leaving one record call on one design. No foreign observation moves on
either pair, so the visibility gate -- a two-bit-per-player word at Ship+0x54
that is not on the wire, stood in for with ownership -- is entirely unexercised
and is labelled a hypothesis.
ghidra/addresses.d/dt.json: 10 entries (the sweep 0x007c2350, the recorder
0x007be340, RecordObservedWeapon, the vector helpers, sizeof(ObservedDesign) =
0x10, the vftable, and ServerPlayer +0x254 / +0xfb). gen_addresses.py merges it
clean at 1320 entries, no duplicate name or address.
Replays lane BR's deep command block for ad-turn27-two-raiders.sav through
sots_turn --turn-commands and compares with bp-pinB-turn28.sav (724528ff).
Verdict: outcome 3. DIVERGED: 1092 leaf difference(s) against a 1166-leaf
do-nothing baseline; 80 closed, 6 regressed; the stream's whole contribution to
the state is /Sim/ModCount (1430 -> 1500, target 1502, residual 2).
Two blockers upstream of the turn: the typed writer drops one usp item in
Game::SpecialProjectNameGen (12 of 43 corpus saves, exactly 12 bytes each), and
--relabel-new-ids refuses by guard G3 because the engine mints no client fleet
ids. The tail's tscr gate is TRUE on this save (253, not 252).
tools/aiorders_to_tcb.py now reads the deep dump's aivec/aistr rows, only at a
word the field map already types as a vector, with the followed count
cross-checked against the begin/end pair.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
findings/subsystems/players-residual.md is the deliverable: every one of the 54
leaves on the reference pair and the 24 on pair 2 attributed to a named
mechanism, with the rung split. 53 of 54 are pass-through -- the block is
unmodelled, not mismodelled, and only player 32's Sav is a number we compute and
get wrong.
Three corrections to the record:
- T31's self-check compared its post-turn result against the pre-turn stored
value, so its 6-of-8 covered only the players whose limit never moves.
- the AI difficulty column IS recoverable from the save, by recomputing BnkEl
under both columns against the value the save carries; 1 AI + 1 non-AI on all
eleven corpus saves.
- BANKRUPTCY_PROTECTION_LIMIT_FACTOR is read fmul dword ptr, so it is a float32
in the image; the decimal disagrees at every max income divisible by ten and
the corpus' seven records land where the two agree (rule 23). formula-gaps Q1
addendum + ghidra/addresses.d/lane-pl.json (3 data entries, fragment validated
to a scratch path, no duplicate names).
verify/results/standalone/{status.json,report.txt} refreshed from a build of main
aabd8a3: the committed copy was stale at 131/55, the real number is 128/54.
T34 RecordObservedDesigns is now readable off the corpus and is NOT an intel
pass: a design's creator registers the design in its own odes, its weapons in
owep, and re-stamps otnL on its techs in otch -- odes on build, otch on creation.
Both are hypotheses until a save exists where a player observes someone else's
design.
Status stays open and is flagged as NOT identifiable: eighteen fields of the
player record split the roster the same way and only two saves carry a non-zero
value. The probe is an entry hook on W2's writer.
`ServerSystem::ComputeOutput` 0x00751fb0 and the whole of `ComputeOutputFromRates`
0x00751bb0, read from the instruction stream to the next function start. This is the
function lane E1 correctly identified as the real blocker on P01/P02/P03/P05/P06 after
its own chain came out 25/25 on BnkEl and did not unblock them.
The shape, and the fact everything turns on: construction points the build queue and
the ship-repair pass do not spend are redistributed over trade / terraform /
infrastructure, and the TRADE share is added to the money channel. Unspent
infrastructure points cascade into the terraform pool and unspent terraform points
cascade into money -- two hops, not one. So a colony with an empty build queue earns
the same money whichever way its sliders point, which is why the turn path's money
sits within one trade point of the projected path on this corpus.
Eleven addresses, five of them dropped as agreeing duplicates. New: the repair pass
0x00751590 (the B1 double-run side effect, and its round robin is provably a `min`),
the construction-point helper 0x00746830, the queue-demand walker 0x008251e0, the
ideal-suitability resolver 0x00745d60, and two helpers with non-standard conventions
that would be silently wrong read as thiscall -- 0x0074c6f0 takes `this` in ESI and
0x007460b0 takes the system in EBX.
Corrections in place: `income-term.md`'s "leftover science points" are leftover SHIP
CONSTRUCTION points (there is no science channel in this function), its
science-cascade bullet moves from inferred to instruction-verified, and
`output-term.md`'s 0x00751fa8 boundary is padding -- the `ret 8` is at 0x00751fa0.
Measured, with the engine change (sots-engine wip/output2 d3ee453):
turn1-state -> turn2-state 209 -> 157 closed 52 regressed 0 (was 51 / 0)
turn2-state -> turn3-state 108 -> 86 closed 22 regressed 0 (was 21 / 0)
Closed 1 per pair, regressed 0. Every prediction in the doc's section 4 held; the one
that was off is named and explained. The strongest check is not the leaf count but
the eleven-save self-check in section 5.3, where every difference between the turn
path and the projected path decomposes to the unit into the build queue's points.
The NVE writer 0x00756300 and its four siblings, `ltis`'s writer 0x00743ec0, the
encounter-placement routine whose unserialised field feeds Eid, the tail phase that
calls the writer, and the resolution of an indirect edge lane B5 left open --
ServerSystem primary vftable 0x00a2044c slot 7 is 0x007480b0, and it writes only a
runtime mask, so it never reaches the wire.
None of the ten collides with an address already in the DB (checked against all 714
addressed entries across addresses.json and every fragment); gen_addresses.py
validated to a scratch path at 1019 entries, no duplicate names.
Standalone report regenerated from the lane worktree:
turn1 -> turn2 209 -> 158 closed 51, regressed 0
turn2 -> turn3 108 -> 87 closed 21, regressed 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
standalone_report.py regenerated from sots-engine wip/turnrecord. The reference pair does
not move by design: T36 does not commit by default, so 209 -> 204, closed 5 / regressed 0
and 108 -> 103, closed 5 / regressed 0 are unchanged. What moved in section 6 is the phase
tally, 14/44 modelled and 8 committed -> 15/44 and 9, from the A2 and D2 merges. verified
stays 0 on both tables.
t36-turnrecord.md carries the measurement the default run cannot show: with
--commit-blocked=T36 and a data root, the reference pair closes 29 leaves and regresses 7
(was 9). Two of the three census leaves close; the third is short by exactly one destroyer
on both pairs, because no phase we run builds a ship. The census model itself reproduces
480/480 archived census leaves over 11 saves with 0 mismatches.
T36 stays blocked: sav and inc on lane E1's budget, and one ship count on unmodelled
construction. A field-granular commit knob is not implementable -- the archived record is
one struct on the wire and has no hole to leave.
Coverage: 32 of 480 census leaves are nonzero anywhere in the corpus; cls1 and cls2's
platform count are unexercised, not verified.
The standalone now models the turn's dominant generator cost -- 16 of a measured 18-22
words -- and lands 4 and 2 short of the two calibrated pairs, which is exactly the
per-call-site ledger's split for those turns. The state block is byte-identical; only
left differs. The answer to 'does it match the oracle' is no, by a stated amount, and
tools/rng_oracle_check.py is the instrument that says so.
The tail's last phase is modelled for the six turn-record fields recoverable from the
wire and checked against the record the game itself archived: 480 fields over 80
player-records, 0 mismatches. It stays blocked; --commit-blocked shows exactly which
five fields are missing and what they cost.
By-product, and probably worth more than the phase: the stored bankruptcy elimination
limit is injective in the maximum-income sum it is built from, so every save states the
per-system output term that blocks ComputeBudget. tools/max_income_oracle.py inverts it
-- 25 player-records over the corpus -- and recovers the protection factor as 3.3 from
the saves rather than from the data files. It also shows the engine's -0.15 divisor
disagrees with the game on 6 of those 25.
divergence unchanged: 209->204 and 108->103, 5 closed / 0 regressed on both pairs.
tools/standalone_report.py drives sots-engine's sots_turn over each
consecutive-turn save pair and diffs the result against the game's own
post-turn save with state_checksum.py, which localises to named leaves and
proves its own coverage by re-serialisation.
turn1-state -> turn2-state baseline 209 diverging, after 204, closed 5
turn2-state -> turn3-state baseline 108 diverging, after 103, closed 5
regressed 0 on both
`regressed` is reported next to `closed` and never netted off. It earned its
place immediately: committing the phase-31 player-status restore turned two
agreeing leaves into disagreeing ones, because the phase writes 1 and the file
carries 4.
The stable-system stand-in feeding the colony pass is a labelled hypothesis and
it survived a changed workload -- the same 3 ntdev leaves closed on both pairs,
six agreements, zero disagreements.
Two things deliberately NOT implemented: the TShn/ltis counters (18 leaves, a
`+1` would close them, but "+1 across one observed turn" is a hypothesis, not a
reading), and the RNG state write-back (an advanced-but-incomplete generator is
wrong in a different way from an untouched one).
dashboard.py gains section 6, reading verify/results/standalone/status.json:
phases modelled/committed per driver, baseline vs after, closed vs regressed,
the subsystem breakdown of what still differs, and the RNG gap. Sections 6-8
renumbered to 7-9; the delta footer tracks the two new counts.
DASHBOARD_README.md documents every number.
findings/control-flow/standalone-scaffold.md has the ranked blocker list.