14 KiB
L4 — what the live AI will emit, written before the module was built
Lane L4, 2026-09-08. Guest VM145 (sots-re-win10-145, 192.168.10.145). Worktree wip/l4 off
main 0117495.
Rule 2: this file is committed before the shim module is written, and it names the falsifier for every claim. Nothing below is a summary of a run; there has been no run.
The question this lane exists to answer: does our emission model produce the same commands the
game does? src/game/ai models the shape of a TurnCommands block and the cost of each
element, and decides nothing. Nobody has ever looked at a real AI block.
0. The instrument, and why it is one detour
Game::StrategySim::ApplyTurnCommandBatch 0x0088f9b0 receives (blocks, n) as two stack
arguments with ecx = sim — read straight off the prologue at 0x0088f9db–0x0088f9e7, where
n is multiplied by 0x1b4 and added to blocks to make the end pointer. At that instant every
player's submitted block is complete and in memory, so one register-transparent entry stub dumps
the entire turn's command traffic: six gates and twenty-seven list lengths per player, plus the
element bytes.
That is a strictly better instrument than counting ModCount bumps, which is what every previous
lane has had. A bump count says how many commands; the block says which lists, how many
elements, and what values.
Second instrument: sixteen register-transparent entry counters (probe_entry.cpp's pattern, its
own table so lane H's set is untouched), on the task bodies and the emission gates. One of them —
StrategyAIAgent::RunTaskList 0x006b3320 — additionally records its pass stack argument into
a global, so every later probe hit is attributed to a pass. That is what turns AI3's P2 from a
count into a measurement.
Rule 20 is the whole design. AITRaid's list-16 emit is the named open item, and a zero at
0x007635f0 means nothing unless AITRaid::Execute 0x0068e670 is also probed: "never ran" and
"ran and emitted nothing" are opposite answers.
Rule 19 control. Both workloads have published oracle bytes. Every configuration is run against them and any configuration that moves a byte has invalidated its own numbers.
1. Workload W1 — turn1-state.sav, one End Turn (the turn 1→2 transition)
Facts already in the corpus, from turn1-state.sav → turn2-state.sav (read this session with
verify/save-reader, not taken from a report):
| players | 8: ids 16, 32, 496, 512 (Species 0/2/0/2) and 528/544/560/576 (Species == 4) |
| player 32 | HomeSys 288 = Ke'Dolarra, PlyrIdx 1 |
ModCount |
0 → 12 |
ResRate |
0.25 → 0.8 on 32, 496, 512; unchanged (0.25) on 16 |
ResTNm |
'' → IND_Waldo (32), DRV_PlsFiss (496), BIO_GnMod (512); '' on 16 |
Status |
0 → 4 on 16/32/496/512; 0 on all four Species == 4 |
| player 32 designs | 5 → 6; the new one is DesID 18, "Honor Lance" |
designIds |
gains 18 and 1712 — the second belongs to player 528, whose design count also goes 18 → 19 |
NMnx |
106 → 109 (ship 1728, fleet 1744, and one more) |
| new fleet | 1744 "Alpha Fleet" at system 288, holding ship 1728 of design 18 |
P1 — the block set
Prediction: n == 4, and the four block+0x04 player ids are 16, 32, 496, 512 in that order.
Falsified if n == 8 — then the Species == 4 factions do submit, they just submit empty
blocks, and AI4 §2's "no StrategyClient at all" is too strong (it would become "a client that
never sets the always-set gate", which is a different and stranger claim). Falsified differently
if the four ids are 0,1,2,3 — then block+0x04 is a player index, not the save id, and every
published reading of that field needs the same correction.
P2 — the four blocks, list by list
| block | gates set | lists non-empty |
|---|---|---|
| 16 (human) | rate only, value 0.25 | none |
| 32 (AI) | rate 0.8, target (IND_Waldo) |
1, 3, 5 — one element each |
| 496 (AI) | rate 0.8, target (DRV_PlsFiss) |
none |
| 512 (AI) | rate 0.8, target (BIO_GnMod) |
none |
Boost, group-4, the three-float Hiver gate and CivilianRatios clear on all four.
That sums to AI4's twelve: 4 rate + 3 target + 3 (player 32's lists) + 2 drivers.
Falsified if: any list outside {1, 3, 5} is non-empty on any block; or a list in {1, 3, 5} carries more than one element; or the human's block carries a list at all; or 496/512 carry a list (they own nothing, so a list element from them would mean a task fired against objects they do not have).
The sharpest falsifier is list 8/10/14 non-empty. Player 32 gets a brand-new fleet (1744) this turn, and a fleet is exactly the thing lists 8/10/14 order. If the AI issues a fleet order on the same turn it creates the fleet, AI4's P2 attribution is wrong by exactly the fleet-order group and the twelve has to be re-derived.
P3 — the element values
- list 3 (
{i32 ordinal, i32 designId, i32 systemId, i32 w}):designId == 18,systemId == 288. Thewword has never been observed and is not predicted. - list 5 (
{i32 systemId, OutputRates}):systemId == 288, i.e. the same system the build order names. - list 1 (
ShipDesignDefframe +i32): the element carries the id 18 somewhere in its first 48 bytes.
Why 18 matters. 18 is not a multiple of 16 and does not come from the master NMnx counter — the
other new design that turn, player 528's, got 1712 from that counter, and 528 submits no
block. So there are two id spaces, and the prediction is that a client-issued design carries a
small client-local id in the command itself, while a server-created object gets a master id.
Falsified if the list-1 element's ids are all ≥ 512 / multiples of 16 — then 18 was assigned
server-side when the command was applied and the design command carries no id at all, which is a
materially different thing for a reimplementation to emit.
P4 — the probes on W1
| probe | prediction | what a miss means |
|---|---|---|
RunTaskList 0x006b3320 |
6 = 3 AI players × 2 passes | 2 ⇒ only one player's task list runs and 496/512 reach ResTNm by some other path; 0 ⇒ the instrument is broken, not the game |
BuildTurnCommands 0x00783780 |
4, one per submitting block | ≠ 4 ⇒ P1 is wrong from the other side |
RequestBuildForTask 0x006cea50 |
> 0 and even-ish — entered on both passes, emits only on pass 1 | entries only in pass 1 ⇒ the caller gates before it, and AI3 §2.3's "the hub returns an empty list on pass 0" is not the whole mechanism |
AssignFleetsAndIssueOrders 0x006c16c0, IssueRouteForFleets 0x006bbd50 |
entered, both passes, zero list-8/10/14 elements | entered + elements ⇒ P2 falsified |
AITRaid::Execute 0x0068e670 |
0 | — |
ClientOrder 0x007635f0 (list 16) |
0 | — |
the six shared planner bodies (AITColonize 0x0068b400, AITEscortGateInvade 0x0068c7c0, AITInvade 0x0068d7a0, AITNodeBore 0x0068e590, AITBuildPoliceShips 0x00690380, AITBuildDeepScanShips 0x006901a0) |
at least one build-shaped body entered in both passes; at most one produces the single list-3 element | none entered ⇒ the list-3 element comes from a task nobody has named, and AI3 §5's path table is not the emission path |
AITAdvanceIdleShips::Execute 0x0068f230 |
entered (priority 0, always last) | — |
IsClaimedByAnotherTask 0x006a8d20 |
> 0 | — |
Stated in advance so it cannot be claimed afterwards: W1 almost certainly cannot settle AI3 §2.4.
A turn-1 AI with one colony and no fleet of its own has nothing to raid, so AITRaid is predicted
not to run at all, and a zero at 0x007635f0 will then be a non-answer about pass 0 — exactly the
"did not fire" vs "fired and found nothing" distinction rule 20 is about. The AITRaid question needs
AITRaid::Execute to be entered, and W2 is the better chance.
Also stated in advance: this probe set cannot settle AI3's P3. An entry counter on
IsClaimedByAnotherTask measures the "called often" half only; the steal branch at 0x006a8ded is
inside the function and an entry probe cannot see it. P3 stays open.
2. Workload W2 — ref-turn2.sav, one End Turn (the turn 2→3 transition)
ref-turn2.sav is turn2-state.sav (same sha256). Corpus facts for 2→3:
ModCount12 → 24;NMnx109 → 111 (ship 1760, fleet 1776"Gamma Fleet");- fleet 1744
"Alpha Fleet"is gone, replaced by fleet 34"Beta Fleet", in transit (FtTrans 1), flight plan one waypointWpt 272,pnd 288, ETA 3, origin = Ke'Dolarra's position; - no
ResTNmchange on any player;ResRatealready 0.8.
AI4 §2.1 attributes the twelve as 4 rate + list 5 + list 3 + list 10 + 2 × list 14 + list 8 + 2 drivers.
P5 — the block
Prediction: n == 4; blocks 16, 496, 512 carry the rate gate and nothing else (no target
gate this turn); block 32 carries the rate gate and lists 3 = 1, 5 = 1, 8 = 1, 10 = 1, 14 = 2,
everything else empty.
P6 — list 14 is two elements against one fleet, and the second field is the discriminator
Lane Q read the element as {i32, i32, bool}; human-turn2-orders.sav (a UI fleet move) carries
exactly one, {1456, 0, true}. AI2's P1 says the AI's bridge calls the adder twice with mode 0 and
mode 1 and the adder keys on (fleet, mode).
Prediction: the two list-14 elements are {F, 0, b} and {F, 1, b} with the same F, and F is
also the fleet id list 8's element names. That converts AI2's P1 from an inference about bump
counts into a read of the values.
Falsified if: both elements carry the same second word (then the adder does not key on mode and the doubling has another cause); or the two fleet ids differ (then it is two orders against two fleets and the "one order costs two elements" rule is wrong); or list 14 has one element (then AI4's two list-14 bumps came from two different fleets and the reference turn moved two).
P7 — which fleet id the order names
Prediction: F == 1744 — the fleet that exists at submit time — and 34 does not appear
anywhere in the block. 34 is created when the order is applied, from the same small-id space as
design 18.
Falsified if F == 34: then the client creates the fleet object before submitting and ships the
new id in the command, which would make the small-id space client-allocated and part of the wire
protocol — a much bigger constraint on a reimplementation than the alternative, because our engine
would have to reproduce that counter exactly to get byte-identical saves.
This is the one prediction I would most like to be wrong, because the falsifier is the more interesting world.
P8 — list 10's first word
Lane AI4 §4.5 refused to name list 10 ({i32, i32, counted i32}) on positional evidence alone.
Prediction: its first i32 is F, and its counted vector holds ship ids — on W2 that means the
single ship 1728. If so, "assign these ships to this fleet" is supported by values rather than
by adjacency, and the list can be named.
Falsified if the counted vector is empty or holds system ids (W2's route is a single hop to 272,
so a {…, 272} tail would make it a route-shaped command instead).
P9 — AITRaid, the second attempt
Player 32 now owns a fleet, so AITRaid has a candidate. Prediction: AITRaid::Execute is
entered on both passes and ClientOrder 0x007635f0 is entered zero times, i.e. list 16 stays
empty and AI3 §2.4's inference holds — but for the reason the probe can see, which is the point.
Falsified if 0x007635f0 is entered with pass == 0 recorded: then pass 0 does emit, AI3's P2
is falsified, and every ModCount arithmetic in ai-order-emission.md that assumes one emitting
sweep is off by a factor.
3. Rule 19 — the controls, and what they are compared against
Published oracle bytes (sha256 prefixes, findings/subsystems/running-the-game.md):
| workload | (Autosave EndTurn).sav |
(Autosave).sav |
|---|---|---|
W1 from turn1-state.sav |
a3f9dc4b… |
ab4ac2d7… |
W2 from ref-turn2.sav |
bb4fd9ac… |
978041ac… |
Three configurations per workload, each a separate launch:
| run | shim.cfg |
purpose |
|---|---|---|
| C | hooks=off |
proves this clone reproduces the oracle at all. VM145 is a ZFS clone; nothing has ever checked that it is byte-faithful. If C fails, the lane reports that and stops — every later number would be measured against an unknown baseline. |
| A | aiorders=on, aiprobes=off |
the block dump alone: one detour |
| B | aiorders=on, aiprobes=all |
the dump plus sixteen entry probes |
Prediction: C, A and B all reproduce both oracle hashes. Falsified if A or B moves a byte —
in which case that configuration's numbers are reported as unmeasured, not as results, and the
sixteen-probe set is bisected with aiprobes=N. Lane H's precedent is explicit that a MinHook
detour has changed this game's behaviour once already, and that the probe module is the one it
happened to.
4. What this lane will not do, said now
- It does not close AI3's P3 (§1, P4). An entry probe cannot see a branch inside the callee.
- It does not test the eleven "free" lists (AI4 P1's untested half) unless one of them turns up populated, which P2/P5 predict will not happen. If one does, that is the result.
- It does not settle the Hiver gate (AI4 P3). The corpus has no Hiver player and this lane is not manufacturing one.
- It reads element bytes, not element types. The dump emits a fixed 48-byte window per element plus the same bytes as ints and floats; decoding to lane Q's records happens offline, in the report, where a wrong record is visible as a wrong value rather than being baked into the instrument. Where a list's in-memory element is larger than 48 bytes the dump says so rather than silently truncating.
- The dump's own coverage check: for every list it walks the node chain and reads
_Mysize, and logs aMISMATCHline if they disagree. A wrong list layout would otherwise print a confident, wrong zero (rule 1).