sots-re/verify/results/compare/recap-misc-compare.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

6.8 KiB

tracecmp report: recap-misc-compare.jsonl

  • build: recap-7584bad-20260908T0615Z started: 2026-09-08T06:47:07Z inline_max: 256
  • calls: 187 compared: 187 diverged: 8 invalid records: 0 warnings: 0
  • coverage: 187 guarded call(s), 63 undeclared write(s) in 23 call(s); 0 hook(s) unstated, 0 contradicted
hook calls modes compared diverged errors
Game::ServerPlayer::OnTechResearched 2 compare:2 2 0 0
Game::ServerSystem::ProcessTurn 140 compare:140 140 0 0
Game::StrategyServer::MoveFleet 45 compare:45 45 8 0

coverage

hook verdict compared regions guards undeclared writes unmodelled
Game::ServerPlayer::OnTechResearched partial abilities, capture_designs, design_masks, inc_mod, modifiers, node_bore_ptr, +8 player 6 in 2 call(s) 5
Game::ServerSystem::ProcessTurn partial bats2, bats_mask, haltv, ibon, infra, inputs, +6 system 15 in 6 call(s) 5
Game::StrategyServer::MoveFleet partial pos, prev_pos, rng, ship[0].range, ship[1].range, ship[2].range, +7 fleet 42 in 15 call(s) 6

Game::ServerPlayer::OnTechResearched — not checked by this run

  • (high) posts EVENT_RESEARCH_COMPLETE / _UNDERBUDGET / _TEMPERANCE on the owner's EventStorage when !silent — the same class of write as B3's defect, and this hook has no replace-mode oracle that could catch it: gotcha 4 in docs/B2.md says a changed save hash on a completion turn is expected and therefore not a finding [guard:player (EventStorage is inline at ServerPlayer+0x29c)]
  • (high) writes every owned system's AI flag (CCC_AIVrus / CCC_AISlv), re-evaluates the arcology civilian cap, cures addiction and clears plague across systems AND ships — writes through pointers to other objects; compare mode must not touch live state, and no region reaches them
  • (high) the pending plague-cure roll (ServerPlayer::RollResearchEvent) — it draws exactly one word from the strategic generator unconditionally; running it in compare mode would consume real randomness. The two words it guards are still cleared and the record says whether it would have fired [this is the extra draw B3 observed on a completion]
  • (medium) TechTree::SetResearched for the Zuul boarding-pod grant — it would mutate the live tree, and it recurses
  • (medium) allocates or frees the node-bore block at ServerPlayer+0x308 — ours has no allocator the game's runtime could free, so replace mode calls the game's own updater -- which means replace mode never exercises our node-bore selection at all [region:node_bore, declared only when the block already exists]
  • guard hits in compare mode: player+0x274:3, player+0x278:3, player+0x27c:3, player+0x2b0:1, player+0x278:1

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 compare 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 compare mode: fleet+0xa0:4, fleet+0xdc:1, fleet+0x10c:1, fleet+0xcc:1, fleet+0xdb:2, fleet+0xe0:26

Game::StrategyServer::MoveFleet: first 5 of 8 divergent call(s)

  • call_id 42 (recap-misc-compare.jsonl:44) side.pos.after.v.y [exact] orig={"t":"f32","v":3.1515913} ours={"t":"f32","v":3.15159106}
  • call_id 79 (recap-misc-compare.jsonl:81) side.pos.after.v.y [exact] orig={"t":"f32","v":1.58417809} ours={"t":"f32","v":1.58417821}
  • call_id 80 (recap-misc-compare.jsonl:82) side.pos.after.v.y [exact] orig={"t":"f32","v":4.21149492} ours={"t":"f32","v":4.21149445}
  • call_id 116 (recap-misc-compare.jsonl:118) side.pos.after.v.y [exact] orig={"t":"f32","v":0.0167649984} ours={"t":"f32","v":0.0167651176}
  • call_id 118 (recap-misc-compare.jsonl:120) side.pos.after.v.y [exact] orig={"t":"f32","v":3.14307928} ours={"t":"f32","v":3.14307904} other divergent call_ids: [154, 156, 157]