Measured over all eleven saves with state_checksum, not derived:
* the reference pair posts TWO events (turn1->turn2) and THREE (turn2->turn3),
and only two players in the whole corpus ever hold an event -- the human and
the one AI empire that owns colonies. The dormant shadow empires pick research
targets every turn and still post nothing.
* order within a player is readable off the ids: the build pass posts before the
research pass. Ids are per player, so no cross-player order is observable.
* the event type is the EvImg string and it is composed at run time --
EVENT_ENEMY_INCOMING_Human carries a species suffix, so the type space is not
an enumeration.
Two corrections in place (rule 11):
* the turn PostEvent is handed is the FRAME, not ModCount. turn2-state.sav has
Frame 2 and ModCount 12, and every event it carries is in bucket EvTurn=2. It
is the post-increment turn: a turn run from a save at turn N posts into N+1.
* the EVENT_NO_RESEARCH gate: 0x00584e50 is TechTree::CollectResearchedTechs,
not a ListAvailableTechs, and the middle test is "nothing was researched on
this turn or later" -- not "no affordable tech". zuul-turn23 exercises it: the
human posts RESEARCH_COMPLETE on turn 22 with no no-research event that turn,
then NO_RESEARCH again on turn 23.
ghidra/addresses.d/lane-ev.json records the gate at 0x0089162a with the argument
order re-read from the instruction stream. Validated with tools/gen_addresses.py to
a scratch path (1121 entries, no duplicate); the shared header is NOT regenerated.
tools/standalone_report.py gains --engine-arg (repeatable), so a lane can feed the
standalone the operator inputs a save does not carry -- the data root, the AI
roster, which blocked phases may commit -- instead of hard-coding them. Every run
records what it was given, in the report header and in status.json.
|
||
|---|---|---|
| .. | ||
| lane-a.json | ||
| lane-a2.json | ||
| lane-ai1.json | ||
| lane-ai2.json | ||
| lane-ai3.json | ||
| lane-b5.json | ||
| lane-b6.json | ||
| lane-c3.json | ||
| lane-d2.json | ||
| lane-e1.json | ||
| lane-e3.json | ||
| lane-ev.json | ||
| lane-g2.json | ||
| lane-h.json | ||
| lane-i.json | ||
| lane-j.json | ||
| lane-k.json | ||
| lane-n.json | ||
| lane-o.json | ||
| lane-p2.json | ||
| lane-q.json | ||
| lane-t.json | ||
| lane-t2.json | ||
| lane-u.json | ||
| lane-v2.json | ||
| lane-w.json | ||
| lane-w2.json | ||
| lane-z.json | ||
| README.md | ||
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.