sots-re/ghidra/addresses.d
alex e9dec36d77 findings: lane AI3 -- the AI stepping order is save player order, and pass 0 writes nothing
Closes five of lane AI2's open items and corrects two published claims.

The stepping order: StrategyServer::ResumePlaying 0x007ddc90 walks the player
array in INDEX ORDER and raises SEResumePlaying at each live player; the app
callback StrategyApp::OnClientEvent 0x00838e10 delivers it inline for humans
and appends it, deduplicated, to the pending queue for AI players. So the AI
players are stepped in save player order, each at most once -- computable from
a save with no live measurement. AI2's search missed it because 0x00b29f98 is
not a pointer to the StrategyApp, it IS the StrategyApp: the enqueue writes the
absolute member address 0x00b29fb4 and never materialises the object base. The
enqueue also has zero direct callers and no vtable slot -- its address is stored
into StrategyServer+0x170 by CreateGame. Lane B6's third blind spot, twice over.

The two passes: `pass` is a tier index, not a plan/act switch. 0x006abb2a picks
between two per-candidate quota fields -- tier 0 takes +0x10, tier 1 takes +0x14
-- and the hub loops `for (i = 0; i <= pass; ++i)`. Every order-emitting exit is
gated pass == 1 (0x006bbd50, 0x006c16c0, 0x006cea50), so pass 0 claims each
task's minimum force in priority order and WRITES NOTHING. That halves the
ModCount arithmetic.

Corrections:
- AI2 §4.2: the nine "no order method" tasks are not planners. All nine emit
  orders at depth 4-9; the depth-4 cut hid it. AITColonize reaches list 7 and
  AITBuildPoliceShips reaches list 3, which is what their names promise.
- AI2 §3: the two priority overrides' flag polarity is inverted. The tunable
  applies when bit 0 of +0x4 is SET, and "committed" is not a supported name.
- AI2 §10.6: the .data invade tunables have no loader. 650 and 750, image
  constants, exactly one reader each and no writer anywhere.

Also: slot 12 named (a preemption permission, 0x006a8d20), slot 13's consumer
found (0x00696620, a range budget -- and the Zuul are exempt, a FOURTH
independent cross-check on AI2's species reading), slot 11's dispatch located.
ServerPlayer+0xf9/+0xfa -- the two bytes that decide whether a player is
AI-controlled -- sit in a hole in the serialised layout and are NOT in the save;
their writer is unfound and is the biggest remaining hole.

Static only; nothing here has run under an instrument. 160 indirect call sites
inside the AI closure are unresolved, so reachability is still a lower bound.
Four predictions with falsifiers in §7.

ghidra/addresses.d/lane-ai3.json: 15 entries, 1,105 -> 1,120, no duplicates,
validated to a scratch path.
2026-09-08 15:29:53 -04:00
..
lane-a.json A: the AIAgent CD blocks - derivation, addresses, and what stays a hypothesis 2026-09-08 07:33:33 -04:00
lane-a2.json lane A2: the alliance mask read from the bytes, and every ModCount writer 2026-09-08 12:44:30 -04:00
lane-ai1.json lane AI2: the AI's task selection loop, the priority table, and the order-method -> TurnCommands map 2026-09-08 14:43:41 -04:00
lane-ai2.json lane AI2: the AI's task selection loop, the priority table, and the order-method -> TurnCommands map 2026-09-08 14:43:41 -04:00
lane-ai3.json findings: lane AI3 -- the AI stepping order is save player order, and pass 0 writes nothing 2026-09-08 15:29:53 -04:00
lane-b5.json B5: the combat resolver's sub-dispatcher is RETREAT, and it is the largest 2026-09-08 12:00:14 -04:00
lane-b6.json findings: ship construction -- the build queue read byte for byte, and the missing destroyer is the AI's 2026-09-08 14:54:03 -04:00
lane-c3.json lane C3: ComputeOutput on the turn path -- the money ComputeBudget actually sums 2026-09-08 14:50:50 -04:00
lane-d2.json lane D2: the ship-design catalogue -- how designs persist, hull size, and the 0x400 flag 2026-09-08 12:45:36 -04:00
lane-e1.json income-term: the output -> money chain read from the instruction stream; the BnkEl oracle goes 6/25 -> 25/25 2026-09-08 13:49:48 -04:00
lane-e3.json lane E3: ten addresses for the visibility record, and the standalone's new distance 2026-09-08 13:38:57 -04:00
lane-g2.json board: lane P2 nav classifier; type 2 is the Liir drive, a naming error not a gap 2026-09-08 12:45:27 -04:00
lane-h.json lane H: five live probes, the oracle restored, and a harness perturbation bisected to one hook 2026-09-08 13:53:36 -04:00
lane-i.json lane I: the complete inlined-draw inventory, and the seven RNG entry points 2026-09-08 10:03:33 -04:00
lane-j.json lane J: the combat resolver, read from the instruction stream 2026-09-08 09:37:02 -04:00
lane-k.json lane K: field offsets belong in the offset schema, and off_RNG was already right 2026-09-08 08:48:25 -04:00
lane-n.json lane N: the population -> base-output term, read and live-verified 2026-09-08 12:11:52 -04:00
lane-o.json lane O: 7 purpose-built saves - issued turn commands, node routes, research-roll-pending 2026-09-08 08:35:13 -04:00
lane-p2.json lane P2: the path solver read from the instruction stream, and OrderFleetMove's three failure bits 2026-09-08 12:43:34 -04:00
lane-q.json lane Q: TurnCommands_v5 reconciled and typed; SAVE_FORMAT section 11 corrected 2026-09-08 09:03:26 -04:00
lane-t.json T: read ServerPlayer::ProcessTurn and StrategyServer::ProcessTurn from the instruction stream 2026-09-08 08:08:36 -04:00
lane-t2.json lane T2: the treaty-turn stamp, and Player.Status's real writer 2026-09-08 14:52:44 -04:00
lane-u.json lane K: StrategyServer::OnAllCombatDone_Tail mapped, 36 phases from the instruction stream 2026-09-08 08:47:08 -04:00
lane-v2.json lane V2: vtable inversion — resolve indirect call edges image-wide 2026-09-08 12:05:05 -04:00
lane-w.json lane W: SvSctOb variant factories; fix the four save_reader.py defects openly 2026-09-08 07:05:09 -04:00
lane-w2.json lane W2: multiplayer Tier 0 played end to end with no server; ModCount/Frame/Status watchpoints; rcex explained 2026-09-08 15:23:52 -04:00
lane-z.json Z: the dominant RNG consumer is trade-raid generation, behind a virtual call 2026-09-08 10:57:12 -04:00
README.md gen_addresses: per-lane fragment dir; duplicate names are a hard error 2026-09-08 05:55:03 -04:00

Per-lane address fragments

addresses.json is a single shared file. When several lanes run concurrently they edit the same lines, and three times on 2026-09-08 one lane's git add swept another's in-flight entries into the wrong commit. Nothing was lost, but authorship and atomicity were.

A lane may instead drop its own file here:

{ "entries": [ { "name": "...", "addr": "0x...", "convention": "...",
                 "prototype": "...", "status": "verified", "source": "findings/..." } ] }

Name it after the lane (lane-d.json). tools/gen_addresses.py merges every fragment in sorted order after addresses.json. A duplicate name across files is a hard error, not last-wins — two lanes disagreeing about an address is exactly the thing we must not paper over.

The integrator folds fragments back into addresses.json once the lane's work is merged.