# L4 — the AI command block, live Companion to `docs/L4-predictions.md` (written and committed **before** the module existed) and to `sots-re/findings/subsystems/ai-order-capture.md` (the full report, with the raw logs). ## What was added `src/shim/hooks/ai_orders.{h,cpp}` — two instruments behind three config keys. **`aiorders=on`** installs exactly one detour: a register-transparent entry stub on `Game::StrategySim::ApplyTurnCommandBatch`. That function takes `(blocks, n)` as stack arguments and multiplies `n` by `0x1b4` to make its end pointer, so at its entry every player's submitted `TurnCommands` block is complete, in memory, at a known stride. One hook there dumps the whole turn's command traffic: six gates, twenty-seven list lengths and 48 bytes per element, per block. **`aiprobes=all`** adds sixteen entry counters in lane H's asm-stub style, in their own table so `probe_entry.cpp`'s set is untouched and `probes=` still means what it meant. Row 0 is `StrategyAIAgent::RunTaskList`, and its stub is hand-written: it reads that function's `pass` stack argument into a global before tail-jumping, so **every later probe hit is attributed to a pass**. That is the difference between counting entries and measuring the two-pass model. **`aiorders.out=`** — the dump goes to its own file as well as `shim.log`. Both halves are separately switchable so they can be given separate rule-19 controls. In the end they did not need to be: the full seventeen-detour configuration reproduced the campaign's published End-Turn oracle byte for byte, on a guest (VM145) that had never been checked against it. ## What it found Full account in the findings document. The three that change this repo: 1. **The AI submits a list-23 element every turn.** Lists 17–27 had never been populated by any workload, so the free half of `ListAdvancesModCount` was read from the instruction stream and nothing else. It is now exercised twice and both turns still cost the measured 12. 2. **Ids in AI commands are client-allocated.** The build order names design `18` and the fleet order names fleet `34` — neither exists in the input save, and the *other* new design that turn (a monster faction's, made server-side) took `1712` from the save's master counter. Two id spaces; the small one travels in the command. `OrderClient` does not model id allocation and now has a named reason to. 3. **Pass 0 emits nothing, measured from the output.** All three pass-1-gated exits were entered in both passes in equal numbers, and the block carries one copy of each element. `OrderClient`'s `EnterTaskPass` gate is confirmed. ## Tests `tests/game_ai/test_live_blocks.cpp` — 44 checks, both captured blocks rebuilt through the public `OrderClient` API from the dumped values, asserting the list profile, the element values, the gate counts and the `ModCount` total for each turn, plus a two-sided check that the list-23 element is free while a list-16 element is not. It is deliberately a **separate binary** from `test_orders.cpp`. That file is the record of what static reading predicted before any of this ran, and it stays that way; this one is the record of what the game did. The agreement between them is evidence only while the two stay independent. Two placeholder ids in `test_orders.cpp` were corrected in place from the capture (the AI fleet order names fleet 34 with a hop to 272, not fleet 1744; the three research targets are techIds 144/90/288). Rule 11: a wrong id in a test is how a wrong id spreads. ## Gates Run as separate commands (rule 13). * `tools/clean_room_check.sh` — **OK** * host `ctest --preset host` — **55/55** * CT111 shim cross-build (`/srv/re-lab/build/sots-engine-l4`, `DIST=/srv/re-lab/shim/dist-l4`) — **exit 0**, exports 66 names identical to the real `binkw32.dll` The generated header was regenerated from `sots-re/ghidra/addresses.json` plus every `ghidra/addresses.d/*.json` fragment (1,209 entries). This lane's fragment is `lane-l4.json`, nine entries: eight `IAITask::Execute` bodies and the list-16 order method. Nine other entries in the regenerated header belong to concurrent lanes' fragments and came along with the merge, as the per-lane fragment mechanism intends. ## One thing that did not work, recorded because it costs a run The turn-1 workload (`turn1-state.sav` + one End Turn) is **not reproducible**. Three runs produced three different post-turn autosaves, differing in exactly one field: the research target of the one AI player that owns nothing. Every other byte — ids, designs, fleets, `ModCount` — is identical. `determinism-oracle.md` verifies the oracle for the `ref-turn2` workload and only for that one; this lane is the first to run the turn-1 workload more than once, and it is now on the record that a byte comparison on that workload is a comparison against noise.