sots-re/ghidra/addresses.d/lane-l4.json
lane-l4 7e6bda3699 L4: the AI's command block, read out of the running game
findings/subsystems/ai-order-capture.md -- two workloads on VM145, every submitted
TurnCommands block dumped at the batch applier, plus sixteen pass-attributed entry
probes on the task bodies and the emission gates.

Headline: our emission model reproduces both real blocks exactly, list for list and
element for element, and both turns land on the measured ModCount of 12. What it did
not have: a list-23 element on every AI turn (the first element ever observed in the
free half of the cost table, and it really is free), client-allocated ids travelling
inside the commands (design 18, fleet 34 -- neither in the input save), and build,
rates and population all naming the same home system.

Two predictions falsified, both usefully: the batch is n = playerCount = 8 with the
four monster factions holding untouched slots, not n = 4; and the fleet order names
the fleet the client has ALREADY created, not the one in the save.

AITRaid's pass-0 question stays open and now says why -- the task never ran on either
board, so the list-16 zero is 'never entered', not 'entered and silent'.

Rule 19: the ref-turn2 control passed byte-identical to the published oracle with all
seventeen detours installed. Separately, the turn-1 workload is NOT reproducible --
three runs, three autosaves, differing in exactly one field: the research target of
the AI player that owns nothing.

ghidra/addresses.d/lane-l4.json: 9 entries (8 IAITask::Execute bodies + the list-16
order method). Raw logs and the two divergent autosaves in verify/results/shim/aiorders/.
2026-09-08 18:01:45 -04:00

77 lines
8 KiB
JSON

{
"_note": "Lane L4 2026-09-08. Nine entry-probe targets for the live AI order capture on VM145. Eight are IAITask::Execute bodies (vtable slot 5) named from the RTTI class that owns the vtable; three of them are SHARED between a task and its `Goal` variant (lane AI3 §5: the paired classes have an identical Execute and differ only in their two GetTarget slots), so the name records the pair rather than pretending there are two bodies. The ninth is the StrategyClient order method AITRaid reaches at depth 1, which is the list-16 producer and the subject of AI3 §2.4. All nine were checked against dumps/functions.json as real function starts and disassembled far enough to confirm a >=5-byte patchable prologue with no branch target inside it, because every one of them is a MinHook target in this lane's shim module. Addresses already carried elsewhere (RunTaskList, IsClaimedByAnotherTask, AcquireFleetsForTask, RequestBuildForTask, AssignFleetsAndIssueOrders, IssueRouteForFleets, BuildTurnCommands, ApplyAllTurnCommands, ApplyTurnCommandBatch) are deliberately NOT re-declared here -- lanes AI1/AI2/AI3/A2 own those names and the generator rejects a same-address-different-name collision.",
"entries": [
{
"name": "AITColonize_Execute",
"addr": "0x0068b400",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITColonize::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITColonizeGoal::Execute is the same address. 48 bytes; a forwarder to the parameterised worker 0x0068b280 called as worker(ecx = agent, this, pass, this->+0x8, &this->+0x20, &this->+0x10) with `edi = this->+0xc` passed as an IMPLICIT REGISTER ARGUMENT (whole-program-optimised custom convention; a reimplementation that ports only the stack arguments passes garbage). Reaches the colonize order (list 7) at depth 4 via 0x0068b280 -> 0x006930f0 -> 0x00578ff0 -> 0x00769640",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#5 -- lane AI3's unlimited-depth direct closure; address re-checked by lane L4 against dumps/functions.json and the prologue disassembled before hooking"
},
{
"name": "AITEscortGateInvade_Execute",
"addr": "0x0068c7c0",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITEscortGateInvade::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITEscortGateInvadeGoal::Execute is the same address. 80 bytes; forwards to 0x0068c5d0 as __fastcall(ecx = this->+0xc, edx = agent) plus 8 stack arguments. Reaches list 7 at depth 4",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#5 -- lane AI3; address re-checked by lane L4"
},
{
"name": "AITInvade_Execute",
"addr": "0x0068d7a0",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITInvade::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITInvadeGoal::Execute is the same address. 160 bytes; forwards to 0x0068d460 with 11 arguments, then maintains this->+0x38 and this->+0x39. +0x39 is computed at 0x0068d80f-0x0068d82a as (0x006a6380(agent, this->+0xc) < 2 * 0x006a6260(agent, this->+0xc)) and is what IAITask slot 11 returns for this class. Reaches list 14 at depth 5",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#4.3,#5 -- lane AI3; address re-checked by lane L4"
},
{
"name": "AITNodeBore_Execute",
"addr": "0x0068e590",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITNodeBore::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Does its setup (0x00685810) and its finaliser (0x0068e090) only when pass == 0, and forwards `pass` to 0x0068a520 in both passes. Reaches list 14 at depth 5 via 0x0068a520 -> 0x006ceef0 -> 0x006c16c0 -> 0x006987e0 -> 0x007634d0. Zuul-flavoured task (the node-bore is a Zuul mechanic), so on a non-Zuul board it may be absent from the task list entirely",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#2.3,#5 -- lane AI3; address re-checked by lane L4"
},
{
"name": "AITRaid_Execute",
"addr": "0x0068e670",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITRaid::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. THE ONE TASK WHOSE PASS-0 BEHAVIOUR IS NOT SETTLED: at 0x0068e89e it calls 0x006b76a0 and then at 0x0068e8b8 loops the list-16 order method 0x007635f0(client, fleetId, 1) over the returned fleets, and NEITHER the call nor the loop carries a `pass` guard of its own. AI3 §2.3's argument says the fleet vector at [ebp-0x28] is empty on pass 0 because it is downstream of the same hub, but [ebp-0x28] was never traced to closure. This address exists as an address so that a zero at 0x007635f0 can be read: entered-and-silent and never-entered are opposite answers (method rule 20)",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#2.4,#8.3 -- lane AI3 named this probe; lane L4 built it"
},
{
"name": "AITAdvanceIdleShips_Execute",
"addr": "0x0068f230",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITAdvanceIdleShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Whole body is pass-1 only: `if (pass != 1) return` at 0x0068f25a. Table priority 0, so it is always the LAST task in the descending-priority sweep -- the AI sweeps up whatever is still idle only after every other task has taken both its minimum and its desired force, which is what the two-tier quota model predicts",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#2.3 -- lane AI3; address re-checked by lane L4"
},
{
"name": "AITBuildDeepScanShips_Execute",
"addr": "0x006901a0",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITBuildDeepScanShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Reaches the BUILD order (list 3) at depth 6 via 0x006ce460 -> 0x006ce360 -> 0x006ce190 -> 0x006bd790 -> 0x006b3bc0 -> 0x00762fd0, the same chain as AITBuildPoliceShips",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#5 -- lane AI3; address re-checked by lane L4"
},
{
"name": "AITBuildPoliceShips_Execute",
"addr": "0x00690380",
"convention": "thiscall",
"prototype": "void __thiscall Game::AITBuildPoliceShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Reaches the BUILD order (list 3) at depth 6, identical chain to AITBuildDeepScanShips. One of the two candidate producers of the single list-3 element the reference AI emits on turn 1",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#5 -- lane AI3; address re-checked by lane L4"
},
{
"name": "StrategyClient_OrderList16",
"addr": "0x007635f0",
"convention": "thiscall",
"prototype": "bool __thiscall Game::StrategyClient::OrderList16(int objectId, bool flag) -- RET 8. THE LIST-16 PRODUCER. Opens with `cmp byte [this+0x15c], 0` -- the End-Turn latch StrategyClient::EndTurn 0x00783be0 sets at 0x00783c59 before BuildTurnCommands runs -- so like every other order method it refuses once the turn has been submitted. Element record for list 16 is {i32, bool} (lane Q), matching the (objectId, flag) pair; list 16 is in the PAYING half of the ModCount table (bump at 0x00821e23 in its applier 0x00821e20). Reached from AITRaid::Execute at depth 1. The method is named for the list it fills, NOT for what the flag means: no save has ever carried a list-16 element and nothing has been observed",
"status": "unverified",
"source": "findings/subsystems/ai-stepping-and-passes.md#2.4 + findings/objects/turncommands-block.md#3 -- lane AI3 named the probe, lane Q typed the element; lane L4 read the prologue and checked the +0x15c latch"
}
]
}