# Live-record bridge readiness architecture Status: **planning draft; contract remains proposed**. This is acceptance-before-implementation for `research-live-record-bridge`, not live-safety evidence. No game, VM, allocator, constructor, or bridge code was executed while producing it. ## 1. Bound facts and reconciliation result The accepted dependency is the integrated static package `verify/results/research-completion-abi-independent/integration-lead-20260910/manifest.json` (binary `dumps/sots.exe`, SHA-256 `970b7de729956a53094c7eb98aba4270aee98e2fed5daf0d39e290013c90c841`). Its scope is static reproduction plus archived-save inspection, not live allocator safety. Two current generated-header statements are unsafe to consume and must be replaced by a dedicated bridge fact channel before implementation: * `include/generated/sots_addresses.h` says `EvDsc` is omitted from duplicate equality. The accepted repaired windows establish the opposite: after action, location, three floats, message and image, `0x00825d40` compares both descriptions through `0x0046f8c0`; a description-only difference is therefore **not** a duplicate. * The same header names `ObservedTech_copy_ctor = RVA 0x0039a184`, an interior inlined-copy site. Fresh pinned-binary reconciliation in `campaign/research/research-live-record-addresses.json` confirms callable helper entry VA `0x0079a150` (RVA `0x0039a150`). It is a three-stack-argument cdecl-style helper: unused allocator-shaped argument, destination, source; the caller removes 12 bytes after its plain `ret`. It has no supported return-value contract. Full-image linear disassembly found exactly two direct calls, both to `0x0079a150`; neither targets `0x0079a184`. The interior site depends on the entry prologue's EBP, SEH, ESI and construction-state setup and is therefore an executable negative control, never a fallback. The dedicated package now records the ObservedTech default constructor `0x008562a0` (`ECX=this`, no stack words, `EAX=this`, plain `ret`) and scalar-deleting destructor `0x00793610` (`ECX=this`, one flags word, `EAX=this`, `ret 4`). Fresh raw captures and a capture manifest are at `verify/results/research-live-record-bridge/run-a247a9d6650d9e0954596cc0/`. The constructor creates the valid empty name string and all scalar defaults. The destructor frees a long name, resets that string, restores base vptr `0x009e22bc`, and frees object storage only when flags bit 0 is set. Vtable bytes at `0x00a2439c` put this destructor in slot zero; the accepted reallocation capture positively shows old embedded elements dispatched through slot zero with a pushed zero, so bridge calls must use flags=0 and must never repeat destruction. The dedicated package now also records the complete PlayerEvent default constructor `0x0084ee30` (`ECX=this`, no stack words, `EAX=this`, plain `ret`) in `verify/results/research-live-record-bridge/run-c1238a40bfc3bfa7c2ff9ba6/`. It establishes every logical field of the 0x74-byte object: vptr, zero ID/location/action/CID, three independent valid empty strings, and three `FLT_MAX` position words copied from `0x00af0dc8`; inactive SSO/allocator storage bytes are not overwritten. The fixed empty-literal/count-zero helper path allocates and frees nothing. Direct PE decoding of the constructor's MSVC FuncInfo records three unwind states: restore the base vptr, then destroy completed EvDsc, then destroy completed EvMsg. The current string is not treated as completed if its constructor helper throws. This establishes static partial-construction ownership only; no throw, allocation or live destructor was executed. The dedicated package now also records the complete PlayerEvent copy constructor `0x007693f0` (`ECX=destination`, stack source, `EAX=destination`, `ret 4`) in `verify/results/research-live-record-bridge/run-36c401e90eb0ec1db4d35865/`. It writes the vptr and EvEID, independently empty-initializes and deep-copies EvDsc then EvMsg, copies EvLoc and all three EvPos words, independently initializes and deep-copies EvImg, then copies EvAct and EvCID. Every string copy uses `0x00425430` with offset zero/count `0xffffffff`; no source owning header is transferred. Direct PE decoding binds three MSVC unwind states: base-vptr restore, completed EvDsc destruction, then completed EvMsg destruction. Construction state advances before each potentially allocating string assignment, so the currently failing string is not treated as complete. This is static partial-copy ownership only; no allocation failure, throw or live destructor was executed. The dedicated package now also records ObservedTech vector append `0x007b7320` (`ECX=vector`, stack source, `ret 4`, no supported return). It distinguishes source inside the live range from source outside it, reserves one slot through `0x007b5820` only when full, deep-copies at the old/new last, and advances last only after normal return. Growth chooses at least `size+1` and normally 1.5x capacity, then follows `0x007b34e0` -> `0x0057e590` -> MSVCR100 new, deep-copies all old values, destroys the old range with flags zero, frees the old block through the matching thunk, and publishes the new three-pointer header. Static SEH state exposes partial-copy cleanup, but an actual throw remains unexecuted and cannot support live-safety acceptance. The reserve window is now bound to the direct raw capture and manifest at `verify/results/research-live-record-bridge/run-a52f7d5ebec657d8923d8402/`. Its full comparison records that the invalidated manual transcription omitted exactly eight presentation spaces while all 49 address/opcode/mnemonic/operand rows match. Only the new 2472-byte stream is the current byte-exact capture; the old 2464-byte stream remains invalidated and must not be cited as raw evidence. The following accepted boundaries may seed the dedicated package, but each callable row still needs its raw-window artifact and exact prototype in that package: PlayerEvent append `0x0086c580` (`ECX=vector`, stack source, `ret 4`); destructor `0x0061ae90` (`ECX=this`, plain `ret`); get/create TurnEvents `0x00885380` (`ECX=EventStorage`, stack turn, `EAX=bucket`, `ret 4`); TurnEvents append `0x00884cb0` (`ECX=outer vector`, stack source, `ret 4`); TurnEvents slot-zero destructor `0x0062e120`; string assignment entry `0x00425430` with allocating worker `0x004249a0` (`ret 8`); MSVCR100 scalar delete/new import thunks `0x00924faa`/`0x00924fb6`. Original `RecordObservedTech`, `EventStorage::PostEvent`, and every research completion root are forbidden. ## 2. Exclusive write set One implementation lane owns exactly these new or modified paths in the assigned engine worktree: * `include/generated/sots_live_record_addresses.h` (generated; never hand-maintained) * `src/shim/live_record/{abi.h,bridge.h,bridge.cpp,fixture_entry.cpp,CMakeLists.txt}` * top-level `CMakeLists.txt` only to add the isolated live-record targets * `tests/shim_live_record/{CMakeLists.txt,unit_tests.cpp}` * `tools/build-live-record-fixture.ps1` It must not edit or link `src/shim/main.cpp`, `src/shim/hooks/research.cpp`, any research hook, or the standalone game model. The architecture/acceptance lane owns exactly: * `campaign/research/research-live-record-bridge.md` * `campaign/research/research-live-record-addresses.json` * `tools/generate_live_record_addresses.py` * `verify/live-record-bridge/{check_package.py,expected-records.json,forbidden-symbols.txt}` * immutable run directories below `verify/results/research-live-record-bridge/` Contract/checkpoint mutations remain canonical campaign transactions. Any expansion of either set requires contract revision before code changes. ## 3. Bridge-only invocation and ownership Build a **32-bit MSVC-2010-compatible** `sots_live_record_fixture.dll`, separate from `binkw32.dll`. A PowerShell controller starts a disposable game process without advancing a turn, loads only this fixture DLL, invokes exported `DWORD WINAPI RunLiveRecordBridgeFixture(void*)`, and exchanges a versioned request/result through a named file mapping. The export validates PE fingerprint/module base and resolves only generated RVAs. The controller records loaded modules and rejects any run where `binkw32.dll` is the campaign proxy or any forbidden decision-root address appears in the fixture import/call audit. This route neither links nor initializes the normal shim entry point. All owning objects stay inside the original process and one compiler/runtime family. The bridge never transfers a `std::string` or vector header across the mapping. Requests contain scalar fields and counted UTF-8 bytes; results contain scalar fields, copied string bytes, vector sizes/capacities, and operation counters. Construction is field-wise through accepted constructors/assignment/copy helpers. Append delegates to the accepted vector helper. Destruction is reverse-order, exactly once, with scalar-delete flags zero for embedded values; only array blocks created by the compatible original runtime are released through its matching service. Each operation owns a journal state (`empty`, `object-constructed`, each string assigned, `element-appended`, `result-copied`, `destroyed`). A deterministic failpoint fires **before** each original call and unwinds only completed states. Actual MSVC allocation exceptions are caught inside the MSVC-built DLL and converted to a result code; no C++ exception crosses the exported WINAPI boundary. The contained-failure case is accepted only when counters show no accepted partial record, no outstanding allocation, no mismatched family, and one destruction per completed owned value. ## 4. Required cases and accounting The fixture package must predeclare cases for empty, spare-capacity and full-capacity vectors; SSO and heap strings for every string field; repeated ObservedTech name update; exact event duplicate; description-only-different event; normal destruction; and one failpoint on a long-string/growth path. Every case records complete resulting ObservedTech, TurnEvents and PlayerEvent fields, first/last/end offsets, event ID/order, helper call counts, allocations by family and size, destructions/frees by object identity, failpoint, return status, forbidden-call count, and execution count. Zero cases, missing records, or unbalanced identities fail rather than skip. ## 5. Resources, manifests, and executable gates No resource is currently leased. Host generation/tests use the assigned paired worktrees and a unique output directory. The 32-bit package requires an immutable compiler/linker/SDK manifest (exact VS2010 tool binaries and hashes), generated-address JSON/header hashes, source bindings, fixture DLL/PDB/controller hashes, original EXE/MSVCR100 hashes, expected-record fixture hash, and command/environment manifest. Runtime uses **VM144 only** after verifying MAC/IP, console/admin access, game/session/process state and housekeeping, then acquiring canonical lease `vm144`. VM140 is excluded. Building on CT111 or another shared host also requires its named campaign lease. The eventual package must make these commands literal and zero-exit (output directory replaced by a new unique path each run): ```text python3 tools/generate_live_record_addresses.py --input campaign/research/research-live-record-addresses.json --output /include/generated/sots_live_record_addresses.h --check cmake -S -B -DSOTS_LIVE_RECORD_TESTS=ON cmake --build --target shim_live_record_unit_tests ctest --test-dir -R '^shim_live_record_' --output-on-failure powershell -NoProfile -File /tools/build-live-record-fixture.ps1 -Source -Out -Manifest powershell -NoProfile -File -Guest VM144 -Fixture -Cases -Out python3 verify/live-record-bridge/check_package.py --engine --re --package --require-source-match --require-binary-match --require-fixture-match --require-all-cases --require-balanced-lifetimes --require-negative-controls ``` `check_package.py` must include executable mutations for raw owning-header transfer, interior/wrong entry, wrong convention, allocator-family mismatch, omitted cleanup, double free, incomplete fields, forbidden roots, zero execution, missing artifacts and source/binary/fixture drift. Host success is not live acceptance. A different verifier session must reproduce the integrated package and at least one mutation after all evidence is bound to one integrated source manifest. ## 6. Readiness blockers and exact next probe The contract remains proposed. Missing items are the generated address header and exact prototypes for PlayerEvent append/destruction, remaining allocation, and TurnEvents calls; verified VS2010 toolchain identity; controller and failure-containment implementation; literal VM144 identity/preflight; expected records; checker; and immutable manifests. The `0x0079a150` versus `0x0079a184` check is complete and recorded in `research-live-record-addresses.json`; it qualifies only the ObservedTech copy-construction row. The shared string-assignment check is also complete in the JSON: `0x00425430` takes ECX destination plus source/offset/count stack arguments, returns the destination in EAX, and removes 12 bytes with `ret 0x0c`. Bridge copy sites use offset zero and count `0xffffffff`; allocation exceptions may propagate, so the fixture boundary must contain them. The ObservedTech constructor/destructor pair is now complete in the JSON, including virtual-slot provenance and the flags-zero embedded-object rule. The ObservedTech vector append/growth boundary is now encoded, including both source-location branches, growth allocation/copy/destruction, unsupported return value, and the static-only exceptional ownership qualification. PlayerEvent default construction at `0x0084ee30` is now complete, including exact writes across the 0x74-byte layout, three string initialization states, ABI/return and static exceptional partial-construction cleanup. PlayerEvent copy construction at `0x007693f0` is now complete, including the full ordered write set, three independent deep-copy states, ABI/return and static partial-copy unwind. The next discriminating static check is PlayerEvent destruction at `0x0061ae90`: bind the complete callable window and encode the exact reverse string-destruction order, vptr transition, ABI/return behavior and any exceptional qualification before relying on it for fixture cleanup. Do not begin bridge implementation until every exposed ABI row is encoded in the dedicated generated-address package.