Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
11 KiB
CB — predictions for the command-stream capture, written before the module exists
Lane CB, 2026-09-08. Worktree wip/cb off main 4f25f1e (the brief said 7444c3b; the
integrator merged L1 in the meantime, so this branch is based on the newer main and says so).
Guest VM146 (192.168.10.146). Committed before ai_orders.cpp is touched and before
anything is built.
Lane RB is building sots_turn --turn-commands <blocks>. This lane supplies its input. A replay
with no real stream is untested and a stream with no autosave to check it against is a log file, so
every prediction below is either about the stream or about the binding between the stream and
the autosave of the run that produced it.
0. What is being changed, and why it is small
Lane L4 already dumps the block set at StrategySim::ApplyTurnCommandBatch — one
register-transparent entry stub, read-only, measured behaviour-neutral on the canonical pair. This
lane does not rebuild that. It adds three things and nothing else:
aiorders.words=<N>— the element window, today a fixed 12 words. List 1 carries a polymorphicShipDesignDefwhose id sits past 48 bytes, so the turn-1 workload cannot be captured completely at 12.aiorders.deep=on— follow the heap. Three payloads in the canonical block are behind pointers and are therefore absent from L4's capture: list 8's route vector, list 10's counted vector, and list 23'sPopulationbody. A replayer cannot reconstruct a fleet move without the route. The follower is deliberately generic — it scans the element window for anything shaped like{_Myfirst,_Mylast,_Myend}and for anything shaped like an MSVCstd::string, and dumps the bytes. It does not know which list it is looking at.- Nothing in the instrument types an element. L4's design point — the dump reads element bytes, not element types; decoding happens offline so a wrong record shows up as a wrong value instead of being baked into the instrument — is kept, and the typed decode moves to a host-side Python step that can be corrected without another VM run.
So aiorders=on aiprobes=off remains exactly one MinHook detour, and the added work inside it
is guarded reads of memory the game already owns.
1. The capture format (defined here because RB may not have landed first)
One JSON file per run under verify/results/turncommands/. Mechanical, and the raw words are the
ground truth:
binding— the input save and both output autosaves, each with size and sha256, plus whether they match the published determinism oracle. A capture whosebinding.outputsis absent or whose hashes do not match the run is worthless for a byte-match and must be treated as such.batches[].blocks[]— one entry per block slot,playerId, the six gates with their payloads, and all 27 lists.- Each element carries
raw_words(the window, verbatim),vectors(each followed heap vector, with its element words),strings(each decodedstd::string), anddecoded— a named record only where the campaign has one, with arecordfield naming it and awirearray giving the values in the order the writer emits them. decodedis regenerable fromraw_wordsbytools/turncommands_capture.py. RB should preferdecoded.wireand fall back toraw_words; if the two ever disagree,raw_wordswins.
The one thing the decoder must get right and that is easy to get wrong: list 3's in-memory
element is in the opposite order from its wire record. Lane L4 read that off the writer
(0x00822870 emits +0x14, +0x10, +0x0c, +0x08, descending); lists 5, 8, 10, 14 and 23 all write
ascending. That is per-list, not a rule, and the decoder encodes it as such.
2. Runs, and what each is for
| run | save | hooks |
aiorders |
watch |
purpose |
|---|---|---|---|---|---|
| C0 | ref-turn2.sav |
off |
— | off |
rule-19 control: the oracle bytes with no instrument at all |
| C1 | ref-turn2.sav |
trace |
on, words=32, deep |
off |
the deliverable — canonical pair capture |
| C2 | ref-turn2.sav |
trace |
on, words=32, deep |
on (modcount) |
the trap multiset, as RB's second check |
| C3 | turn1-state.sav |
trace |
on, words=32, deep |
off |
the non-deterministic pair, block + its own autosave |
| C4 | turn1-state.sav |
trace |
on, words=32, deep |
off |
the same turn twice |
3. Predictions
P1 — C0 reproduces the oracle. (control)
(Autosave EndTurn).sav = 66,732 B, bb4fd9ac89f41e3b; (Autosave).sav = 67,219 B,
978041acd168b56e. If this fails, the guest is not the reference guest and nothing else in
this lane means anything; lane L4 already reproduced these on VM145 and lane L5 held VM146, so a
failure here is a lab fact, not a finding.
P2 — C1's autosaves are byte-identical to C0's, so the capture binds.
The deliverable stands or falls on this. Lane L4 ran seventeen detours on this same workload and got the oracle bytes; this run has one, doing strictly more reads inside it. If C1's autosaves differ from C0's, the capture is a stream that changed the turn it recorded, it is unusable for a byte-match, and the report must lead with that (rule 19).
P3 — the block set on the canonical pair is exactly lane L4's, plus the three payloads.
Two batches: seq=1 at load (n=1, pid 16, all 27 lists empty) and seq=2 at End Turn (n=8).
Slots 0–3 are pids 16, 32, 496, 512; slots 4–7 are pid 0 with uninitialised gate payloads.
Only pid 32 is non-empty, with list sizes 3:1, 5:1, 8:1, 10:1, 14:2, 23:1. Every rate gate on the
four real players is set; no research-target gate is set on any player this turn.
New, and the reason for the run:
- P3a — list 8's route vector has count 1. Its single value is the destination system id.
L4 could not read it and noted that
turn3-state.sav's waypoint says 272. Predict 272. Falsifier: any other id, in which case the waypoint in the output save is not the route in the command and the replayer has to carry both. - P3b — list 10's counted vector has count 1. L4's reading is "at system 288, fleet 34, [one
object]". Predict the value is a ship id that already exists in
ref-turn2.sav— the pre-existing ship being folded into the newly allocated fleet 34. Falsifier: a small client-allocated id in the 18/34 space, which would mean the client allocates the ship too and the id-allocation hole L4 left open is wider than one counter. - P3c — list 23's
Populationvector is 24 bytes, i.e. six words, at a stride the dump does not name. Predict the six words are not all zero and that they differ between the turn-2 and turn-1 captures (L4 saw the element's trailing int go+1on turn 1 and−1on turn 2, so the body is turn-dependent and not a constant the replayer can ignore).
P4 — C2's trap multiset is exactly ten bumps, and they attribute.
The paying half of pid 32's block is 3:1 + 5:1 + 8:1 + 10:1 + 14:2 = 6 elements, list 23
being free; plus 4 research-rate gates, one per submitted block, set unconditionally. Total
10 — which is the number lane W2 measured on this turn from the other end, before anyone had
seen the block. Predicted per-EIP:
| trap EIP | count | what |
|---|---|---|
0x00821a87 |
4 | prologue gate +0x0c, research rate, one per submitted block |
0x0084946e |
1 | list 3, build |
0x0086c3e9 |
1 | list 5, system rates |
0x0088bf01 |
1 | list 10 |
inlined at ApplyTurnCommandBatch+0xabc |
2 | list 14, the AI's two (fleet, mode) elements |
| list 8's site | 1 | fleet move |
Falsifier: any total other than 10, or any bump attributed to a list this capture reports empty.
That would mean the counter is not the length of the command stream and the whole
ModCount-from-the-block claim in src/game/ai/orders.h is wrong.
Rule 19 for C2 specifically: the watchpoint module has never been run in the same process as the block dump. If C2's autosaves move off the oracle, C2 is reported as perturbing and C1 remains the deliverable — the trap set is a bonus check, not the capture.
P5 — C3 and C4 differ in exactly one word of the stream.
Both from turn1-state.sav. Predicted block: pid 32 with lists 1:1, 3:1, 5:1, 23:1, three
research-target gates set (pids 32, 496, 512) and the human's clear, four rate gates set.
The prediction that matters: C3's and C4's block sets are byte-identical except for player 512's
research-target payload at block+0x10, and each run's autosave differs from the other's only in
p512.ResTNm and the derived checksum. That is Rung B's claim in its sharpest form — if the two
streams differ only where the two autosaves differ, then the sim is a function of the stream and
the decision layer is the only thing that is not.
Falsifiers, and each is worth more than the prediction:
- The two blocks are identical and the two autosaves differ. Then the stream does not determine the save, Rung B is not reachable by replay alone, and the divergence is downstream of the command — which contradicts L4 §3.1, which saw it in the block.
- The two blocks differ somewhere else as well (an id, an ordinal, a route). Then the client-allocated id space is also per-process and a replay has to carry ids it cannot derive.
- Player 512's target lands on a tech outside the six-member
XNCset L4 named. Then k is larger than six and Rung C's tie-set claim has to start one level up.BIO_GnModhas already been observed once, so this is not unlikely; a secondBIOvalue would settle that the resolver leaves the family.
P6 — list 1's design element yields its id and its name at words=32.
"Honor Lance" is a short-string-optimised std::string inside the element, and design id 18
is named by list 3 in the same block. Predict the widened window shows the string and a word
equal to 18 inside the list-1 element. Falsifier: no 18 anywhere in 128 bytes, in which case
L4 §1 P3 stands as it is — the design command does not carry its own id and the id is inferred.
P7 — what this lane will not be able to say.
Stated in advance so a green capture is not read as more than it is (rule 15):
- Two turns, one AI empire with anything to do, a board with no contact. Lists 2, 4, 6, 7, 9, 11–22, 24–27 will be empty in every block of every run, exactly as they have been in all eleven corpus saves and both of L4's runs. The capture exercises six of twenty-seven lists and one of six gates on the canonical pair, two of six on turn 1. Everything the replayer does with the other twenty-one lists is untested by this lane and must be labelled as such.
- The generic vector follower can produce a false positive — three consecutive words that
happen to look like a vector. The count bound and the readability probe make that unlikely, not
impossible. Any
aivecline on a list whose record has no vector is to be read as noise until a second run reproduces it. - Nothing here says what list 10 or list 23 mean. It says what bytes they carry.