diff --git a/campaign/board.md b/campaign/board.md index fb814c9..f67b4c9 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -135,3 +135,11 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | Game::AISystem::Write never reads its object | objects | verified | high | 100% | 2026-09-08 | Lane A: it builds an EMPTY AISituation on the stack and writes that. Every AISys body on disk is two empty weight maps REGARDLESS OF GAME STATE. Worth knowing before anyone tries to read AI behaviour out of a save | | ten AIAgent element layouts UNEXERCISED | verify | backlog | — | 0% | 2026-09-08 | Typed but hypotheses, flagged by lane A: AIAttr entries, all four weight maps, prs2, dsh, NBStab, NMBlst, nalat, CmbR, NumCL, apr - every one count 0 in all ELEVEN AIAgent blocks across our saves. They come from the WRITERS, not from bytes, and conformance agrees, but nothing exercises them. test_stream.cpp::test_aiagent_block populates every one and round-trips (and asserts the trap directly: a two-element nalat must produce two COMPLEX nodes) - that test is the only thing exercising them. Four containers ARE exercised: dnnc (0/1/2), NPrv (1/2), NTecS (2/3), AINumSys (28). Settling the rest needs a longer multi-AI save = turns played | | Game::TacReport carried, not typed | objects | backlog | — | 0% | 2026-09-08 | Lane A left it deliberately: computed count plus three trailing scalar runs the linear pass cannot resolve, and it sits two levels under NCmbR which is 0 everywhere. Carried as Nodes rather than typed on a guess | +| turn driver map | control-flow | verified | high | 95% | 2026-09-08 | Lane T read BOTH drivers BYTE FOR BYTE from objdump over the raw image, not the decompiler: StrategyServer::ProcessTurn 0x007dc6c0-0x007dcd67 (32 phases) and ServerPlayer::ProcessTurn 0x00891340-0x00891783 (12 phases). CORRECTION TO MY BRIEF: ServerPlayer::ProcessTurn is NOT the phase driver - it is the per-player economy/research half; StrategyServer::ProcessTurn IS the spine. Verified call sites of our pieces: ProcessFleetMovement->MoveFleet phase 8; ServerSystem::ProcessTurn phase 11 (`mov ecx,[Systems+i*4]; call`, NO STACK ARGS, confirming B4); ServerPlayer::ProcessTurn(dt) phase 13 (dt forwarded by fstp [esp]); ComputeBudget player phase 1; ProcessResearch player phase 5. Also settled: the TWO StrategyServer bases 4 bytes apart (every StrategyServer_off_* in addresses.json is the S+4 frame EXCEPT off_RNG) and TWO per-turn counters - BeginProcessTurn bumps S+0xc (ModCount, what the research code stamps), ProcessTurn bumps S+0x8, which nobody has named | +| CORRECTION: turn-spine.md deferred tail was WRONG | control-flow | verified | high | 100% | 2026-09-08 | turn-spine.md 2.4 step 11 said the end-of-turn tail is deferred when encounters are pending. IT IS NOT. The `if` at 0x007dcb15 is an INLINED std::vector DESTRUCTOR - the `je` skips only the operator delete and both arms converge at 0x007dcb38. There is NO branch on the encounter snapshot anywhere in the function. And FUN_00794ad0 never built an encounter list: it builds the fifteen ship-action type ids. Encounter detection is a different function, dead last. THIS IS THE SECOND TIME TODAY the inlined-destructor-looks-like-a-branch trap produced a wrong published finding (lane A hit it on Streamable::Write and got it right). Treat any decompiler `if` wrapping a whole tail as suspect until the converge point is checked | +| ResearchRollPending SETTLED | verify | verified | high | 100% | 2026-09-08 | Lane T, by full displacement scan of +0x3b4 - only TWO consumers in the image. Site A ServerPlayer::ProcessTurn+0x2ac runs AFTER ProcessResearch (+0x165), NOT before as the board previously said: `if (ResT && ResErrRoll && 0.5f < progress/Cost) { roll; ResErrRoll = false; }` - threshold is a STRICT `<` against 0.5f at 0x00a2c788, and THE CLEAR IS INSIDE THE BRANCH, so a failed ratio test leaves the flag set. Site B OnTechResearched+0x51 clears unconditionally given ResT==def. So the flag survives into the next turn IFF progress/Cost <= 0.5 at the end of this one - which is exactly why three sessions gave 0/1/0. SAVE RECIPE: ResErrRoll is a serialized tag right after ResTNm so it can be EDITED INTO a save - set it true, point ResTNm at a high-cost tech with near-zero progress, fund research to >= 1.5x cost | +| REPO-WIDE CORRECTION: roll branch costs ONE OR TWO RNG words | verify | verified | high | 100% | 2026-09-08 | EVERY coverage note in the repo says the research-event roll draws "exactly one NextFloat". Lane T: RollResearchEvent itself draws one, but the branch it fires (ServerPlayer_OnResearchRollSucceeded 0x00889d60) draws a SECOND NextInt on the plague path and posts EVENT_PLAGUE_OUTBREAK; the rebellion path allocates an AIRebellion and CANCELS the research. Any RNG accounting that assumes one word is wrong the first time that branch fires - and it has never fired in three sessions, so nothing has caught it | +| ResErrRoll arming mechanism UNKNOWN | objects | backlog | — | 0% | 2026-09-08 | Lane T flagged rather than papered over: NOTHING was found that ARMS the flag during play. The only writes are the two clears, ServerPlayer::Read, the constructor (`mov WORD [esi+0x3b4],1` - born TRUE), and a virtual setter pair with no locatable caller. "Born true, consumed once" fits every observation but IS A HYPOTHESIS. The save recipe does not depend on it | +| unread driver: OnAllCombatDone_Tail | control-flow | backlog | — | 0% | 2026-09-08 | THE BIGGEST UNREAD BLOCK IS NOT IN ProcessTurn AT ALL. No bankruptcy, no turn results, no turn events, no autosave run in the turn driver - they are all in StrategyServer::OnAllCombatDone_Tail 0x007d92a0, a SECOND driver no lane has read. Highest-value next control-flow target | +| player_turn hook prepared (boundary narrowed) | phase2 | mapped | high | 70% | 2026-09-08 | Lane T built the descriptor with compile-time coverage() (9 notes), Result regions for the three phases needing nothing but the player, roll_flags + rng as observations ours never writes, guards over the whole ServerPlayer and the TechTree header. DELIBERATELY NARROWED: phases 2/3/6 (savings, aid records, refund) depend on ComputeBudget's slots and ProcessResearch's overBudget, both STACK LOCALS of the original. The three ways to reach them are calling ComputeBudget ourselves (repairs ships - audit #6), reading the nested hooks (audit #5, SELF-FULFILLING), or inferring from the Sav delta. They are GUARDED, NOT CHECKED; formulas written and unit-tested but not in the verdict. docs/T-turn-driver.md carries the prediction incl. which checks are WEAK BY CONSTRUCTION on the reference save | +| INTEGRATOR NOTE: two gate failures caught on lane T merge | meta | verified | high | 100% | 2026-09-08 | Both gates failed on merge and both were real, which is the argument for running them SEPARATELY rather than &&-chained: (1) clean-room exit 1 - a raw FUN_00889d60 identifier had reached docs/ in the public-capable engine repo; fixed by using the name the lane itself had already put in addresses.json (ServerPlayer_OnResearchRollSucceeded). (2) The shim CROSS-BUILD failed -Werror=unused-function on player_turn.cpp - the file is WIN32-only and the lane could not build it on the notes host, exactly as it flagged. Host ctest passed both times and would have hidden both. A lane that cannot cross-build its own shim TU must say so, and the integrator must run the CT111 shim build before pushing |