diff --git a/campaign/board.md b/campaign/board.md index 5070dfb..fb814c9 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -129,3 +129,9 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | SvSctOb side findings | objects | verified | high | 100% | 2026-09-08 | `indsys` = Game::SVSOIndependentSystems, whose Read AND Write are both the shared `ret 4` stub - its EMPTY FRAME IS CORRECT OUTPUT, not truncation. SVSOMonitor derives from SVSODerelict (its Write calls Derelict's first). SVSOSots::Read accepts NPCPlr/hastraps that Write NEVER EMITS - read-only back-compat, not a recovery hole. And THE RECOVERY IS WRONG about SVSOCrowDefenders::dsys - it is inside the ndsys loop, which no save can settle. EncIDs 7/8/14/21 and ScnObj have factory entries and recovered serializers but NO OCCURRENCE anywhere - deliberately carried as Nodes, not typed | | four oracle fixes landed, byte-neutral | verify | verified | high | 100% | 2026-09-08 | Lane W fixed all four OPENLY with 12 new tests: 36 -> 48 tests passing, --strict exit 0 on all four saves, state_checksum coverage PROVED on all four with unchanged rebuilt byte counts. BYTE-NEUTRALITY PROVEN AT ITEM GRANULARITY: dumping every save before and after, the OFFSET SEQUENCES ARE IDENTICAL (38,933/39,843/40,300/35,771 offsets, no item boundary moved); the /CreateParams and /Sim/players digests DO change, correctly, because they hash typed values, and the value-byte deltas balance EXACTLY (odet x3 + p1 x4 = 292/295/295/238, to the byte). NUANCE: defects 3 and 4 were an ABSENCE in save_reader.py, not an error - ShipRecs and civr were both A(...,"any"), so the fix ADDED typed shapes rather than retyping a field, a bigger change than my brief implied. srbd turned out BEHAVIOURALLY CONFIRMED not inferred: it takes 0/1/3/4 across players and every non-zero count is followed by exactly srbd x 5 scalars (turn3 player 4: srbd==4, 20 items) - read as a field those trailing scalars have no explanation. `nv` is WEAKER: only ever 0 or 1 in these saves, so the multi-pair case is synthetic | | save_reader silent-agreement failure mode | verify | verified | high | 100% | 2026-09-08 | Forced into the open by lane W's tests: save_reader.py's walker types "." items from its OWN kind catalog, not the schema passed in - so on the real saves the catalog and the old schema agreed with each other AND WERE BOTH WRONG. Its failure mode is SILENT AGREEMENT, not the framing desync the schema-driven engine would have suffered. Two independent-looking checks that share a hidden assumption are one check | +| AIAgent blocks typed | engine | verified | high | 95% | 2026-09-08 | Lane A: NAMED COVERAGE 98.0 -> 99.9% on all four saves; opaque items 748 -> 37 (turn1/2/3) and 521 -> 37 (zuul). Conformance 74 shapes/769 items -> 86/838, STILL 0 MISMATCH, all twelve new bindings 0 wire-only 0 shape-only. Round trip byte-identical throughout; state_checksum --strict still coverage PROVED, 0 error 0 warn, unchanged. Ratchet 97.5 -> 99.8. LOAD-BEARING FINDING: Streamable::Write (0x006c6f00) is UNCONDITIONAL - the decompiler shows a large `if` wrapping everything from `lnat` on, which reads exactly like the branch that defeated CD, but it is an INLINED std::vector DESTRUCTOR whose operator delete this database marks noreturn; the disassembly shows both paths converging at 0x006c72e8. So the recovery's all-branches view and a single record are THE SAME VIEW - the opposite of lane W's TurnCommands result, for a concrete reason | +| dsh is a map count, not a scalar | objects | verified | high | 100% | 2026-09-08 | Lane A: `dsh` is _Mysize at +0x240 of the std::map whose _Myhead is at +0x23c - the same head@0/size@4 layout the campaign pinned on ServerSystem::NVO. Node _Isnil at +0x21 makes the value pair> i.e. pid/trns. THE RECOVERY LISTED IT AS A PLAIN FIELD; typing it that way DESYNCHRONISES THE READER. Also lnat/lat are scalars not elements (recovery marks both [element]); saves confirm - nalat is 0 everywhere and both are present anyway | +| CORRECTION to lane W: StreamableEnum | objects | verified | high | 100% | 2026-09-08 | Lane A found StreamableEnum writes a FRAME CONTAINING ONE INT, not a bare int, so VectorHelper> is a framed array of one-int frames. SysMem, mts and nalat had been typed as vector. BYTE-NEUTRAL - all three have count 0 in every save, which is EXACTLY WHY IT WAS INVISIBLE - but the previous typing was wrong. Lane W had flagged those two as hypotheses; this is what checking one looked like, and it is the argument for flagging them | +| 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 |