CB: predictions and the capture format, before the instrument changes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
This commit is contained in:
parent
c67374d2e3
commit
4457249b88
1 changed files with 181 additions and 0 deletions
181
docs/CB-predictions.md
Normal file
181
docs/CB-predictions.md
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
# 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:
|
||||
|
||||
1. **`aiorders.words=<N>`** — the element window, today a fixed 12 words. List 1 carries a
|
||||
polymorphic `ShipDesignDef` whose id sits past 48 bytes, so the turn-1 workload cannot be
|
||||
captured completely at 12.
|
||||
2. **`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's `Population` body. 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 MSVC `std::string`, and
|
||||
dumps the bytes. It does **not** know which list it is looking at.
|
||||
3. 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 whose `binding.outputs` is 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 decoded `std::string`), and `decoded` — a named record
|
||||
**only where the campaign has one**, with a `record` field naming it and a `wire` array giving
|
||||
the values in the order the writer emits them.
|
||||
* `decoded` is regenerable from `raw_words` by `tools/turncommands_capture.py`. RB should prefer
|
||||
`decoded.wire` and fall back to `raw_words`; if the two ever disagree, `raw_words` wins.
|
||||
|
||||
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 `Population` vector 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 `+1` on turn 1 and `−1` on 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 `XNC` set L4 named.* Then k is larger
|
||||
than six and Rung C's tie-set claim has to start one level up. `BIO_GnMod` has already been
|
||||
observed once, so this is not unlikely; a second `BIO` value 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 `aivec` line 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.
|
||||
Loading…
Add table
Reference in a new issue