sots-re/verify/results/compare/recap-golden-trace.md
alex 05095e93da lane R: golden-trace recapture with guards on the live game
Five hooks recaptured, build recap-7584bad-20260908T0615Z, no source change.

- ProcessResearch: the over-budget event is now a compare divergence
  (side.events.after.v.next_id orig=4 ours=3) instead of a 609 KB save diff.
  15 calls over 5 turns, 3 diverged, RNG matched 15/15.
- ComputeBudget: 4284 compared, 0 diverged, guard quiet - but only 20 distinct
  states and 13 of 22 slots always zero.
- MoveFleet: 8 of 45 calls diverge by 1 ULP of position. B4's clean verdict was
  a one-sample verdict.
- First guarded captures for OnTechResearched (RollResearchEvent draw fired and
  matched), the colony turn and MoveFleet.
- Guards mapped SetResearched live and found an undeclared ObservedTech append.

VM140 released at the main menu, hooks=trace.
2026-09-08 03:06:43 -04:00

51 lines
6.9 KiB
Markdown

## tracecmp report: recap-trace.jsonl
- build: recap-7584bad-20260908T0615Z started: 2026-09-08T06:18:02Z inline_max: 256
- calls: 3630 compared: 0 diverged: 0 invalid records: 0 warnings: 0
- coverage: 3630 guarded call(s), 9 undeclared write(s) in 4 call(s); 0 hook(s) unstated, 0 contradicted
| hook | calls | modes | compared | diverged | errors |
|---|---|---|---|---|---|
| Game::ServerPlayer::ComputeBudget | 3592 | trace:3592 | 0 | 0 | 0 |
| Game::ServerSystem::ProcessTurn | 28 | trace:28 | 0 | 0 | 0 |
| Game::StrategyServer::MoveFleet | 7 | trace:7 | 0 | 0 | 0 |
| Game::TechTree::ProcessResearch | 3 | trace:3 | 0 | 0 | 0 |
### coverage
| hook | verdict | compared regions | guards | undeclared writes | unmodelled |
|---|---|---|---|---|---|
| Game::ServerPlayer::ComputeBudget | partial | budget, inputs, research_alloc | budget_object | 0 | 4 |
| Game::ServerSystem::ProcessTurn | partial | bats2, bats_mask, haltv, ibon, infra, inputs, +6 | system | 6 in 3 call(s) | 5 |
| Game::StrategyServer::MoveFleet | partial | pos, prev_pos, rng, ship[0].range, ship[1].range, ship[2].range, +7 | fleet | 3 in 1 call(s) | 6 |
| Game::TechTree::ProcessResearch | partial | events, node[0], node[105], node[106], node[107], node[108], +270 | player, tree_header | 0 | 5 |
#### Game::ServerPlayer::ComputeBudget — not checked by this run
- (high) slots 1, 2, 3, 4, 7 and 11 are produced by callees this milestone does not model (per-system output, trade, ship-carried population, a second manager, the build-queue spend) — they are copied out of the original's own output and back into the same slots, so they match BY CONSTRUCTION and prove nothing [declared input boundary; see budget_inputs.h]
- (high) ServerSystem::ComputeOutput repairs damaged ships in orbit — replace mode runs the original a second time on a scratch Budget to harvest the six unmodelled slots, so that repair happens TWICE per turn in replace mode and nothing in the trace would show it [guard:budget_object does not reach the ships; unverified]
- (medium) the difficulty-mods row from StrategyServer::GetDifficultyMods — not reachable from a ServerPlayer, so the two relevant entries are fitted constants measured from the B1 trace rather than snapshotted inputs
- (low) the research-allocation vector's heap block — only the element count is compared; the three words are heap pointers the default policy ignores
#### Game::ServerSystem::ProcessTurn — not checked by this run
- (high) the addiction sweep raises MoraleEvents, which are constructed and appended to the system's capped morale history — the same class of write as B3's defect. sim::ProcessColonyTurn does compute the morale events (ColonyTurnResult), but the hook never emits them: DescribeMoraleEvents is dead code, so they are neither compared nor logged [guard:system]
- (high) every callee: the plague pass, imperial and civilian growth, the resource debit, in-orbit refuel, slaves, rebellion and the build queue — declared input boundary -- ProcessTurn is a dispatcher and only the words it writes itself are modelled. The callees raise EVENT_SLAVES_DEAD, EVENT_SYSTEM_REBELLION_CONTINUES, the plague events and SEBuildCompleted, create ships and bump per-player ShipRecords counters [guard:system covers the system object only, not the other objects]
- (medium) ApplyInfraBonus / ApplyPopBonus read the owner's home-system id, and the build queue writes the owning ServerPlayer — writes through a pointer to another object; no region reaches the player
- (low) ProcessRebellion is the pass's only RNG consumer and its draw count is data-dependent — the generator IS a declared region, so a moved post-state is visible and names the system whose rebellion fired -- it is reported, not modelled
- (medium) replace mode is refused for this hook — our side models the dispatcher's own writes and none of the callees, so a replace run would silently skip a colony's whole turn. There is therefore no oracle layer behind the compare for this hook
- guard hits in trace mode: system+0x1a4:12, system+0xd8:1, system+0x170:1, system+0x238:4
#### Game::StrategyServer::MoveFleet — not checked by this run
- (high) on arrival: dispatches SEFleetArrived and runs one of three arrival handlers by destination kind (enter system / join fleet / stop at point) — declared input boundary -- an arriving call is expected to differ in all of it, and none of it is declared, so the compare says nothing about arrivals [guard:fleet sees the fleet's own words; the event and the system do not]
- (high) on departure: cancels every still-acting ship (with a log line each) and calls ServerSystem::FleetDeparts, which rewrites the system's ownership bits — writes through pointers to ships and to the system
- (medium) the tanker top-up refuels other ships in the fleet — the per-ship range regions would show it, but ours does not model it, so a fleet with a tanker diverges for a known reason
- (medium) a node-line waypoint's step comes from the stutter profile — NodeLineStep / BuildStutterSegments are written and unit-tested but not wired in; the hook steps every waypoint type as speed x dt, so a node-line leg is knowingly mis-stepped and only its type is recorded [declared gap: docs/B4.md]
- (medium) a missed probabilistic jump scatters the fleet in a random direction — the direction is a second draw whose mapping is not modelled; ours leaves the position alone and reports the scatter distance, so the generator region diverges by one word on a miss
- (medium) the route revalidation and the waypoint list itself — declared input boundary; the waypoint vector is not a region
- guard hits in trace mode: fleet+0xa0:4, fleet+0xdc:1, fleet+0x10c:1
#### Game::TechTree::ProcessResearch — not checked by this run
- (high) posts EVENT_RESEARCH_OVERBUDGET on the owner's EventStorage in the same branch that sets node.flag = 2 — the message text is composed from the tech name, so ours cannot synthesise it; it would have to be posted through the game's own event API. This is the defect that made a clean compare false: replace mode's autosave differed from the oracle by exactly this one event [region:events (EvNxID now diverges instead of passing silently)]
- (medium) posts EVENT_TECHS_UNLOCKED for nodes that became available this turn — the trailing unlock loop makes no draw and writes no node, but it does build a names list and post an event [region:events]
- (high) TechTree::SetResearched on completion: the turn/order stamps, the child unlock cascade, the recursive research of zero-cost children, and the owner's OnTechResearched callback — its own milestone (B2); the callback writes live player state that compare mode must not touch, and it consumes one extra RNG word [guard:player, guard:tree_header]
- (medium) bumps the tree's completion-order counter (TechTree+0x20) — part of SetResearched; the per-node `order` word is compared but the counter it comes from was not a region [guard:tree_header]
- (low) writes a completion line to the game log — log text is not simulation state