sots-re/verify/results/compare/cr-replace1.md
alex 7dcc66bc66 lane CR: our code ran instead of ProcessResearch on a real completion; the oracle missed by 16 leaves, all of them OnTechResearched's
Replace mode was tried live on a turn that actually completes a tech, with a
two-process hooks=off oracle established first on that exact (save, procedure,
route). Verdict: game/sim/research stays compared.

What displaced: all 13 tech-tree leaves the turn moves -- 2 from the pass itself
and 11 from the SetResearched cascade -- produced by our code in live game memory,
with the original's ProcessResearch never executing.

What did not: 16 leaves, every one written by ServerPlayer::OnTechResearched.
Five player tech-effect fields (OutMod, ConMod[0..2], ResTNm), one ObservedTech
element, two event records plus EvNxID, and five derived leaves behind them.

Also: ref-turn2 + one End Turn does NOT complete a tech, so every research oracle
before this one was taken on a quiet turn; and a config that names all 27 registered
hooks off and passes check_shim_configs.py still installs six detours, because the
M0 stub and the FPU module's four sampling detours have no hook. key.
2026-09-09 10:05:48 -04:00

4.6 KiB

tracecmp report: R1.trace.jsonl

  • build: cr-618ccb1-20260909T131556Z started: 2026-09-09T13:42:14Z inline_max: 256
  • calls: 4 compared: 0 diverged: 0 invalid records: 0 warnings: 0
  • coverage: 3 guarded call(s), 1 undeclared write(s) in 1 call(s); 0 hook(s) unstated, 0 contradicted
hook calls modes compared diverged errors
Game::TechTree::ProcessResearch 3 replace:3 0 0 0
Shim::SelfTest::Fill 1 trace:1 0 0 0

coverage

hook verdict compared regions guards undeclared writes unmodelled
Game::TechTree::ProcessResearch partial events, node[0], node[105], node[106], node[107], node[108], +271 player, tree_header 1 in 1 call(s) 8
Shim::SelfTest::Fill complete buf - not watched 0

Game::TechTree::ProcessResearch — not checked by this run

  • (medium) posts EVENT_RESEARCH_OVERBUDGET on the owner's EventStorage: ours reproduces the decision and the id sequence, so region:events compares next_id, but the composed EvDsc/EvMsg text is not reproduced and no region can see it — text comes from the game's string table, which the engine must not carry; ours posts into its own EventStorage and writes only the counts into the scratch copy, so no live byte moves and replace mode posts nothing at all [region:events]
  • (low) composes EVENT_TECHS_UNLOCKED's message from the unlocked techs' names — the trigger and the list are modelled (SetResearched's availability sweep plus the tail collector, both read off the instruction stream), so region:events compares next_id; the names come from the game's string table, so the message is composed from node indices instead and is not the game's text [region:events]
  • (high) TechTree::SetResearched in REPLACE mode: only its TechTree half runs, and only when research.replace_cascade=on — with the flag OFF (the default) nothing of the cascade runs, so a replace run leaves the completed node unstamped and no tech unlocked. With it ON, the four TechNode words (costRP, turnAvailable, turnResearched, order) and the tree's completion-order counter are written live, and the ServerPlayer half is still not: no event is posted, no ObservedTech element is appended and no tech effect is applied. Neither setting is a full displacement of the completion path; the pair measures where the boundary is [guard:player, guard:tree_header]
  • (high) ServerPlayer::OnTechResearched's tech effects: the ~90 hard-coded ServerPlayer field writes, the plague-cure masks, the design-option bitmasks and the species tech flags — B2's milestone. ours models only the two parts of the callback this hook's regions can see -- the observed-tech append and the RNG word RollResearchEvent draws before its branch (one word on a missed roll, two on a fired plague roll) -- and the rest is what the player guard reports [guard:player]
  • (high) the research-event branch RollResearchEvent takes when its roll beats the odds (ServerPlayer::OnResearchRollSucceeded: the plague and AI-rebellion event paths) — RollResearchEvent draws one NextFloat unconditionally and that draw IS modelled -- but that is only the cost of REACHING the branch. A FIRED roll costs one or two words: the plague path draws a SECOND word (NextInt) to pick an owned system and posts EVENT_PLAGUE_OUTBREAK, while the rebellion path allocates an AIRebellion at ServerPlayer+0x3b8 and CANCELS the current research (no further draw). The branch is entered only for the plague and AI-rebellion tech families, whose odds are 0 everywhere else, and it has never been observed firing in three sessions -- which is why every earlier note in this repo said 'exactly one NextFloat' and nothing caught it. If it is ever entered, region:rng is the check [region:rng]
  • (medium) constructs the ObservedTech element it appends to ServerPlayer+0x274 — ours models the append DECISION -- RecordObservedTech de-duplicates by tech name, so it decides whether the vector grows -- and moves the scratch header's byte span by one 0x2c element per append. The element's own fields (turn_first, turn_last, detected, the name string, with) are not built, and no region can see them [region:observed_techs]
  • (low) the tree's completion-order counter (TechTree+0x20) is read pre-call, not modelled as a region — the per-node order word IS compared, and it is stamped from a counter ours seeds from the pre-call read and advances itself; the counter's own final value is only seen by the tree_header guard [guard:tree_header]
  • (low) writes a completion line to the game log — log text is not simulation state
  • guard hits in replace mode: tree_header+0x20:1