128 lines
7.6 KiB
Markdown
128 lines
7.6 KiB
Markdown
# 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`:
|
|
|
|
1. `ResT == NULL` — no research target;
|
|
2. `TechTree::CollectResearchedTechs(out, turn, INT_MAX, sort=1)` came back **empty**, i.e.
|
|
**no tech finished on this turn or later**;
|
|
3. `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 leaf
|
|
`Events/Events/.`, the new count leaf `Events/Events/.[0]`, the new bucket
|
|
`Events/Events/.[EvTurn=2]`.
|
|
* pair 2: **closed 3, regressed 0** — `Events/EvNxID`, `Events/Events/.[0]` (1 -> 2), the new
|
|
bucket `Events/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.
|