sots-re/ghidra/addresses.d
alex 1893751ffa PAR: roll parity -- an AI client's per-turn RNG word count is not fixed at any scope
The hypothesis under test was the lockstep discipline: that each run of an AI client consumes a
fixed number of draws regardless of the path it takes, so a reimplementation could keep the
generator aligned with the right COUNT and order of draws while getting the decisions wrong.

It is false, and it fails at four scopes. Measured with a new bracket on
StrategyClient::OnResumePlaying over the per-client generator at +0x134, six runs across VM140 and
VM145, five fresh processes; every unpinned run's autosaves are byte-identical to the published
oracle, so the instrument is behaviour-neutral (rules 19 and 26 both discharged).

  across clients   turn2->turn3: 3 / 0 / 0 words for AI players 32 / 496 / 512; human 0
  across turns     client 32: 3 words on turn 2, 7 on turn 1
  across processes client 512 makes ONE cl_RandRange call on turn 1 -- the research-target
                   tie-break at 0x006a8495, phase 18 -- and it cost 1 word in one process and
                   3 in another, because RNG_NextInt is an unbounded rejection loop
  per site         RNG_Chance costs ZERO words at p<=0 and p>=1

Twenty-one live draw sites in an AI turn, in twelve functions (plus two provably dead ones); two
fired on the reference turn, three on turn 1. Only three are unconditional, and all three only
given that their enclosing function was called. Six of client 32's seven turn-1 words come from
the ship-design composer 0x006ad700, which is also where the only loop-carried draw lives.

Also: cl_RandFloat 0x00579c70, a third cl_* RNG facade, found twice independently. It reaches
RNG_NextFloat by a TAIL JUMP, so no rel32 sweep for the entry points can see it -- which is why
ai-turn-logic.md 5's 'zero NextFloat calls from the AI module' reads as true and is not. All 29
call sites of the three facades are inside the AI band: the cl_* RNG facade is AI-only surface.

Positives for the engine: the AI draws from nothing but its own client's generator (foreign_words
0 on every bracket), the human client draws nothing at all, and the per-turn cost is single digits.
Rung B is unaffected. Rung C needs the decisions.
2026-09-08 19:33:00 -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 gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -04:00
lane-ai4.json AI4: what an AI turn emits, and what each command costs in ModCount 2026-09-08 16:27:58 -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 gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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-ev.json lane EV: what a turn actually posts, and two corrections to events.md 2026-09-08 15:40:07 -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-g3.json gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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 gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -04:00
lane-l1.json board: lane L5 - reference pair non-deterministic; interest literals verified with a failing control; verified column held at 0 on principle 2026-09-08 17:55:47 -04:00
lane-l4.json L4: the research tie set -- k = 6, named, and the mechanism measured twice 2026-09-08 18:30:05 -04:00
lane-l5.json L5: the money chain's float widths, measured live at a boundary 2026-09-08 17:49:35 -04:00
lane-n.json gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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-par.json PAR: roll parity -- an AI client's per-turn RNG word count is not fixed at any scope 2026-09-08 19:33:00 -04:00
lane-pl.json lane PL: decompose the /Sim/players residual by mechanism; the bankruptcy protection factor is a float32 2026-09-08 16:36:45 -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-rb.json RB: replay a recorded turn's commands -- ModCount is reachable, and the rates frame's memory order is not its wire order 2026-09-08 19:01:35 -04:00
lane-sv.json gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -04:00
lane-t.json gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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 gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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 gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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-w3.json W3: NVO.TShn's writer trapped live, its gate named, and 158/158 on the corpus 2026-09-08 16:41:09 -04:00
lane-z.json gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -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.