7.6 KiB
EV — posting events into the save's turn bucket (P11)
Roadmap item 5. The engine already models the event log (game/events: turn-bucketed
storage, per-bucket dedup, the FLT_MAX position sentinel, the act == 0 && no subject && no position -> 2 rule). What was missing is that the standalone never wrote any of it into the
save it produces. This lane wires that up for the one event a turn on the reference pair
actually posts that the standalone can compute, and reports the rest as blocked with the
blocker named.
Nothing here carries the game's displayed text. The engine holds EVENTSUM_* / EVENTMSG_*
keys only; the text is resolved at run time through a lookup the caller supplies, which the
standalone builds from the operator's own installed string table (--data).
1. What a turn actually posts (measured, whole corpus)
Not derived — read out of the eleven saves in verify/results/saves with the state-checksum
tree. Only two players in any corpus save ever hold an event at all: the human (PlyrIdx 0)
and the one AI empire that owns colonies (PlyrIdx 1). Every other player's EvNxID is 0 on
every save, including the two AI shadow empires that do pick research targets.
Reference pair turn1-state.sav -> turn2-state.sav — the whole turn posts two events:
| player | bucket | id | image | act | loc |
|---|---|---|---|---|---|
| 0 (human) | EvTurn=2 |
1 | EVENT_NO_RESEARCH |
1 | 0 |
| 1 (AI) | EvTurn=2 |
1 | EVENT_SHIPS_BUILT |
0 | 288 |
Pair 2 turn2-state.sav -> turn3-state.sav — three:
| player | bucket | id | image | act | loc |
|---|---|---|---|---|---|
| 0 | EvTurn=3 |
2 | EVENT_NO_RESEARCH |
1 | 0 |
| 1 | EvTurn=3 |
2 | EVENT_SHIPS_BUILT |
0 | 288 |
| 1 | EvTurn=3 |
3 | EVENT_RESEARCH_OVERBUDGET |
1 | 0 |
Order matters and is visible in the ids: within player 1's turn-3 bucket the construction
event is id 2 and the research event is id 3, so the build pass posts before the research
pass. Ids are per player (EvNxID lives on the player's own storage), so no cross-player
order is observable and none is assumed.
The bucket's EvTurn is the post-increment turn: a turn run from a save at turn N
posts into bucket N+1. In the standalone that is game.sim.frame after H00.
Across the whole corpus the only other images seen are EVENT_SHIPS_BUILT,
EVENT_RESEARCH_COMPLETE, EVENT_TECHS_UNLOCKED, EVENT_RESEARCH_OVERBUDGET,
EVENT_FLEET_ARRIVED, EVENT_FLEET_EXPLORED, EVENT_FLEET_MULTIPOINT_NONODE,
EVENT_LABACCIDENT_SMALL, EVENT_COLONY_NEWSETTLERS, EVENT_ENEMY_INCOMING_Human and
EVENT_NO_RESEARCH. None of the tail's event phases (T18, T21, T32) fires on either
reference pair — measured, not assumed; on other corpus saves they clearly do.
2. The no-research condition, corrected
The standalone's condition was no research target && at least one available tech. That is
two thirds of the original's and it over-fires by three players on the reference pair.
The original's is three tests, all of which now have a name in ghidra/addresses.json:
ResT == NULL— no research target;TechTree::CollectResearchedTechs(out, turn, INT_MAX, sort=1)came back empty, i.e. no tech finished on this turn or later;TechTree::FindFirstAvailableTech() != NULL, i.e. at least one node is in state 2.
Test 2 was missing here and it is not decoration. zuul-turn23-fleet23.sav exercises it: the
human posts EVENT_NO_RESEARCH on turns 2..15, posts EVENT_RESEARCH_COMPLETE on turn 22
and no no-research event that turn, then posts EVENT_NO_RESEARCH again on turn 23. That
is exactly test 2 suppressing the event on the turn a tech landed.
Tests 2 and 3 are computable from the wire: a TechState row's St is the node state
(0 hidden, 1 parent researched, 2 available, 3 available and selected, 4 researched) and
TAcq is the turn it was researched.
Test 1 is not fully computable. ResTNm gives the target the save was written with, but
three of the four real players on the reference pair acquire a target during the turn, and
that is AI research selection (game/ai). The stand-in is the operator-supplied AI roster,
--ai-player N, which the standalone already carries for T31:
HYPOTHESIS (workload: the whole 11-save corpus). An AI-controlled player that begins its turn with no research target acquires one before the no-research check, so it never posts
EVENT_NO_RESEARCH. Support: across the corpus no AI player posts that event on any of roughly ninety player-turns, while the human posts it on every turn it has no target. Falsified by a single AI player-turn carrying the event.
So P11 stays blocked, commits only under --commit-blocked=P11, and even then posts
nothing for a player the operator named as AI and nothing at all without a text table.
3. Prediction (written before the build; rule 2)
Baseline moved under this lane: main is now eafbc5f (lane T2). Re-measured with this
lane's own binary before any change: pair 1 209 -> 131 (closed 78, regressed 0), pair 2
108 -> 72 (closed 36, regressed 0). The brief's 209 -> 157 / 108 -> 86 predate T2. The
numbers below are against the re-measured baseline, so the lane's own contribution is
pair 1 131 -> 127 and pair 2 72 -> 69.
Run A — --data <root> --ai-player 1 --ai-player 2 --ai-player 3 --commit-blocked=P11.
P11 posts exactly one event, for player 0, into bucket EvTurn = frame:
EvEID 1 (pair 1) / 2 (pair 2), EvDsc and EvMsg from EVENTSUM_NO_RESEARCH /
EVENTMSG_NO_RESEARCH, EvImg = "EVENT_NO_RESEARCH", EvLoc = 0,
EvPos = {FLT_MAX, FLT_MAX, FLT_MAX}, EvAct = 1, EvCID = 0; EvNxID 0 -> 2 (pair 1),
2 -> 3 (pair 2).
- pair 1: closed 4, regressed 0 —
Events/EvNxID, the empty-collection leafEvents/Events/., the new count leafEvents/Events/.[0], the new bucketEvents/Events/.[EvTurn=2]. - pair 2: closed 3, regressed 0 —
Events/EvNxID,Events/Events/.[0](1 -> 2), the new bucketEvents/Events/.[EvTurn=3].
Player 1's four (pair 1) / three (pair 2) event leaves stay: they need EVENT_SHIPS_BUILT,
which needs a ship the turn builds, which needs an AI-generated build order — the same
blocker B6 and T36 already name.
Run B — the control, no --data, no --ai-player, no --commit-blocked. closed 0,
regressed 0; the phase reports the condition and posts nothing.
Falsification
| # | if the model is wrong this way | symptom |
|---|---|---|
| F1 | the AI-picks-a-target hypothesis is wrong | with the roster supplied the event is missing for a player the oracle has one for; without it, four posts and roughly fifteen regressed leaves on pair 1 |
| F2 | the bucket turn is the input turn, not the incremented frame | the bucket lands at EvTurn=1; closed 1, regressed 2 |
| F3 | the position sentinel is written as +inf rather than FLT_MAX |
bucket appears, EvPos/.[0..2] diverge; closed 3, regressed 3 |
| F4 | the act == 0 -> 2 rule is applied where it should not be (act here is 1) |
EvAct diverges; regressed 1 |
| F5 | the writer emits a trailing node on a record the engine synthesised rather than parsed | the post-turn save's coverage drops below the input's, visible in the report's coverage line |
| F6 | the "researched this turn" gate is wrong | no visible effect on either reference pair (nothing is researched on either turn); it would show as a spurious post on the turn a corpus save completes a tech |
Run B is the control for F1: it is the same code with the input withheld, and its over-fire count is a direct measurement of how much of the phase the AI roster is carrying.