16 KiB
Live-record bridge readiness architecture
Status: planning draft; contract remains needs-revision. 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.hsaysEvDscis omitted from duplicate equality. The accepted repaired windows establish the opposite: after action, location, three floats, message and image,0x00825d40compares both descriptions through0x0046f8c0; 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 incampaign/research/research-live-record-addresses.jsonconfirms callable helper entry VA0x0079a150(RVA0x0039a150). It is a three-stack-argument cdecl-style helper: unused allocator-shaped argument, destination, source; the caller removes 12 bytes after its plainret. It has no supported return-value contract. Full-image linear disassembly found exactly two direct calls, both to0x0079a150; neither targets0x0079a184. 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 the complete PlayerEvent destructor 0x0061ae90
(ECX=this, no stack words, plain ret, no supported return value) in
verify/results/research-live-record-bridge/run-a4f6a9922bf5800747bfdfc6/. It destroys the three
independent strings in reverse construction order: EvImg, EvMsg, then EvDsc. Each capacity-at-least-
0x10 branch frees that string's owned buffer through scalar-delete thunk 0x00924faa, and every
path resets the string to empty SSO before proceeding. Only after all three strings are empty does
the helper replace PlayerEvent vptr 0x00a21958 with base vptr 0x009e22bc. It takes no deleting
flags and never frees enclosing PlayerEvent storage. The exact capture byte-matches both accepted
dependency and integrated independent captures. This remains static ownership evidence; no live
destruction, allocator or exception behavior 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.txtonly 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.mdcampaign/research/research-live-record-addresses.jsontools/generate_live_record_addresses.pyverify/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):
python3 tools/generate_live_record_addresses.py --input campaign/research/research-live-record-addresses.json --output <engine>/include/generated/sots_live_record_addresses.h --check
cmake -S <engine> -B <host-build> -DSOTS_LIVE_RECORD_TESTS=ON
cmake --build <host-build> --target shim_live_record_unit_tests
ctest --test-dir <host-build> -R '^shim_live_record_' --output-on-failure
powershell -NoProfile -File <engine>/tools/build-live-record-fixture.ps1 -Source <engine> -Out <win-build> -Manifest <toolchain-manifest>
powershell -NoProfile -File <run-controller> -Guest VM144 -Fixture <fixture.dll> -Cases <expected-records.json> -Out <vm-run>
python3 verify/live-record-bridge/check_package.py --engine <engine> --re <re> --package <vm-run> --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 needs-revision. Missing items are the generated address header and exact prototypes for PlayerEvent append, 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. PlayerEvent destruction
at 0x0061ae90 is now complete, including reverse EvImg/EvMsg/EvDsc cleanup, conditional matching-
family frees, empty-SSO resets, final base-vptr transition, ABI/no-return contract and static-only
exception qualification. The next discriminating static check is PlayerEvent vector append at
0x0086c580: bind its complete callable and growth windows and encode source-location branches,
capacity growth, copy/destruction order, header publication, ABI/return and partial-copy unwind.
Do not begin bridge implementation until every exposed ABI row is encoded in the dedicated
generated-address package.