RB: lane CB's real capture replays to the same result, and brings the seeds and the heap payloads

CB's VM146 capture goes through the JSON->tcb adapter and the replayer unchanged and reproduces
this lane's canonical result from a separate run on a different guest: 108 -> 62, closed 46,
regressed 0, ModCount 24. It carries the first AI seeds any capture has held (32/496/512), and
its deep dump closes two gap rows: list 8's route is [272] and list 10's vector is [1728], a
ship the input save already contains.
This commit is contained in:
alex 2026-09-08 19:07:13 -04:00
parent 7a41246450
commit ce8b3e66b4
2 changed files with 68 additions and 5 deletions

View file

@ -125,9 +125,34 @@ overrides it to `?` and says why in its own docstring; **CB should drop the list
`RECORDS` rather than rely on the adapter to mask it**, because the JSON is the capture of record `RECORDS` rather than rely on the adapter to mask it**, because the JSON is the capture of record
and a wrong typing in it will outlive this note. CB's `raw_words` are unaffected and remain right. and a wrong typing in it will outlive this note. CB's `raw_words` are unaffected and remain right.
Two things CB's dump has that L4's does not, and that the `.tcb` format already has fields for: ### 1.2 CB's real capture, run
`aiseed` (the per-client construction seeds) and `aivec` (the heap vectors — which turn list 8's
route from `v1` into `v1:<hop>` and remove one whole row from the gap list in §4). CB's own VM146 capture landed while this lane was closing and it goes straight through the adapter
and the replayer unchanged: `verify/results/turncommands/cb-turn2to3.tcb`. It reproduces this
lane's canonical result exactly — **108 → 62, closed 46, regressed 0**, `ModCount` 24 — from a
completely separate run of the game on a different guest. It also brings two things L4's dump
could not:
* **The AI seeds, observed live:** netId **32 → `0x9dfad635`**, **496 → `0xb482b2d8`**,
**512 → `0xfb81e993`**, all `pinned=false` (observed, not forced). These are the first seeds any
capture has carried, and they are what makes this a turn record a future `game/ai` can be
*re-derived* against rather than replayed from.
* **The heap payloads**, which close two rows of the gap list below outright:
* **list 8's route is `[272]`** — one hop to system 272. Lane L4 §5.3 recorded this as
"`turn3-state.sav`'s waypoint says 272 but the capture does not prove it". It is proved now.
* **list 10's counted vector is `[1728]`** — a **ship** id, and one that exists in the input
save. `{systemId 288, fleetId 34, ships [1728]}` is now a typed reading with values, and
"assign these ships to this fleet at this system" fits all three. Still not a proof of the
command's *name* — the effect has never been observed — but the payload's type no longer
leaves it open.
Those two elements move from `incomplete` to `declined` in the replay report: the capture is now
complete and the *subsystems* are what is missing.
CB's capture also does the one thing neither of my files did and every future capture must:
it records the input save's hash **bound to the output autosaves' hashes**, both of which match
the published determinism oracle, with a `hooks=off` control run named. That binding is what makes
a capture usable as a byte-match oracle at all.
**The converter.** `tools/aiorders_to_tcb.py` turns lane L4's shim dump into a `.tcb` mechanically: **The converter.** `tools/aiorders_to_tcb.py` turns lane L4's shim dump into a `.tcb` mechanically:
it applies the per-list field mapping, undoes the one list whose writer runs backwards, reinterprets it applies the per-list field mapping, undoes the one list whose writer runs backwards, reinterprets
@ -238,9 +263,9 @@ but not applied.
|---|---|---|---| |---|---|---|---|
| 1 | **list 3 build, list 1 design** | **ship construction.** No phase in this engine builds a ship. The command carries a queue ordinal, a design id and a system; the effect is `srb`/`sri`, `Maint`, the savings debit, a hull id and `ShipIDs`. Nine leaves on the canonical pair. | engine work; lane B6/E2 have the map | | 1 | **list 3 build, list 1 design** | **ship construction.** No phase in this engine builds a ship. The command carries a queue ordinal, a design id and a system; the effect is `srb`/`sri`, `Maint`, the savings debit, a hull id and `ShipIDs`. Nine leaves on the canonical pair. | engine work; lane B6/E2 have the map |
| 2 | **list 1, list 3, list 8, list 10, list 14** | **the client's id allocator.** The build order names design **18** and the fleet order names fleet **34** — objects that do not exist in the input save, allocated *client-side before submission*, while the server's own master counter (`NMnx`) issued 1712 and 1776 the same turn. **Two id spaces**, and the small one is part of the wire protocol. A reimplementation that allocates on apply produces a structurally correct save with every AI-created id wrong. | **a watchpoint**, not a week of reading: break on the write that produces 18 and 34 | | 2 | **list 1, list 3, list 8, list 10, list 14** | **the client's id allocator.** The build order names design **18** and the fleet order names fleet **34** — objects that do not exist in the input save, allocated *client-side before submission*, while the server's own master counter (`NMnx`) issued 1712 and 1776 the same turn. **Two id spaces**, and the small one is part of the wire protocol. A reimplementation that allocates on apply produces a structurally correct save with every AI-created id wrong. | **a watchpoint**, not a week of reading: break on the write that produces 18 and 34 |
| 3 | **list 8 fleet move** | **the route's hops.** The capture records the route's length and not its contents, because the dump does not follow the vector. One more indirection in the dumper. Also needs (2): the route belongs to a fleet the save does not contain. | one dumper change | | 3 | ~~**list 8 fleet move** — the route's hops~~ | **CLOSED by lane CB's deep capture (§1.2): the route is `[272]`, one hop.** What remains is row 2 — the route belongs to a fleet the input save does not contain. | done |
| 4 | **list 5 system rates** | **the memory field order of the rates frame** (§3). One correspondence pinned, six unread. | one UI run, or one save-sourced capture | | 4 | **list 5 system rates** | **the memory field order of the rates frame** (§3). One correspondence pinned, six unread. | one UI run, or one save-sourced capture |
| 5 | **list 10** | **a name and a meaning.** `{systemId, fleetId, counted vector}`; "assign these ships to this fleet at this system" fits and has never been tested. The vector's contents are unread. | a hook on `0x0088bed0` | | 5 | **list 10** | **a name and a meaning.** The payload is fully read now (§1.2): `{systemId 288, fleetId 34, ships [1728]}`, and 1728 is a ship the *input* save already contains. "Assign these ships to this fleet at this system" fits every word. The **effect** has still never been observed, so it is still not named. | a hook on `0x0088bed0` |
| 6 | **list 14 fleet task** | **what the two modes do.** Two elements per AI fleet order, modes 0 then 1; the interface deposits one, mode 0 only. The *cost* is settled; the *effect* is not read at all. | a hook on the inlined applier | | 6 | **list 14 fleet task** | **what the two modes do.** Two elements per AI fleet order, modes 0 then 1; the interface deposits one, mode 0 only. The *cost* is settled; the *effect* is not read at all. | a hook on the inlined applier |
| 7 | **list 23 population** | **the `Population` body**, 24 bytes behind a vftable that the capture window does not follow. Free in `ModCount`, so it has never been forced. | one dumper change | | 7 | **list 23 population** | **the `Population` body**, 24 bytes behind a vftable that the capture window does not follow. Free in `ModCount`, so it has never been forced. | one dumper change |
| 8 | **the research-target gate** | **the techId → tech-name map.** The wire carries an integer (144, 90, 288); the save carries a name. The client resolves it *off the command* and passes a `char*`. The ids are not `index * 16` and are not indices into anything we hold. Today the capture can carry the observed name and the replay TRANSCRIBES it — reported in its own column, because that is not a reimplementation. **Three data points now exist**: 144 → `IND_Waldo`, 90 → `DRV_PlsFiss`, 288 → `XNC_TrnsMorr2`. | a watchpoint at the gate payload, or a read of `0x006c8580` | | 8 | **the research-target gate** | **the techId → tech-name map.** The wire carries an integer (144, 90, 288); the save carries a name. The client resolves it *off the command* and passes a `char*`. The ids are not `index * 16` and are not indices into anything we hold. Today the capture can carry the observed name and the replay TRANSCRIBES it — reported in its own column, because that is not a reimplementation. **Three data points now exist**: 144 → `IND_Waldo`, 90 → `DRV_PlsFiss`, 288 → `XNC_TrnsMorr2`. | a watchpoint at the gate payload, or a read of `0x006c8580` |

View file

@ -0,0 +1,38 @@
tcb 1
meta source cb-c1-turn2to3.txt
meta input turn2-state.sav
meta input_sha256 ab4ac2d7e2977260a4de916f65c7ecc6dd5b0094dac4bb51241317232ea17ab4
meta output (Autosave EndTurn).sav 66732 bb4fd9ac89f41e3bc0db2af08b18ce83417521ac4bcee695fc9fa6ce16e30948 oracle=True
meta output (Autosave).sav 67219 978041acd168b56ed8eb3f5e42e78d5e70eae6e6517d75e659a5eb7ca3d60921 oracle=True
meta control c0-hooksoff-turn2 (hooks=off, same guest, same click path; byte-identical to this run)
meta lane CB capture_version 1 build cb2-20260908T2251Z guest VM146
meta extension vN:a,b -- a vector whose contents ARE known; bare vN still means unknown and must still be refused
meta batch seq=2 n=8
seed 32 0x9dfad635
seed 496 0xb482b2d8
seed 512 0xfb81e993
block 0 16
gate 0 rate 0.25
block 1 32
gate 1 rate 0.8
list 1 3 1
elem 1 3 0 i2 i18 i288 i0
list 1 5 1
elem 1 5 0 i288 f0.0 f1.0 f0.0 f0.0 f0.0 f0.0 i0
list 1 8 1
elem 1 8 0 i34 v1:272
list 1 10 1
elem 1 10 0 i288 i34 v1:1728
list 1 14 2
elem 1 14 0 i34 i0 b1
elem 1 14 1 i34 i1 b1
list 1 23 1
elem 1 23 0 i288 ? v6:12422480,1,2,862581960,1000000000,0
block 2 496
gate 2 rate 0.8
block 3 512
gate 3 rate 0.8
block 4 0
block 5 0
block 6 0
block 7 0