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.
6.8 KiB
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]