# Fresh static observations Static evidence only. It does not establish live allocator safety, a compatible replacement allocator, or replacement acceptance. Raw local evidence and exact regeneration commands are in `objdump-2026-09-09-ownership.txt`; the owner-supplied input is `dumps/sots.exe`, SHA-256 `970b7de729956a53094c7eb98aba4270aee98e2fed5daf0d39e290013c90c841` / MD5 `9969481c39f4b33a8a21c48b62abee4c`. The original dedup archive's first-window provenance was overturned by independent review and Astra decision `d-d2a9b8be6399a6abaa0e05a5`. Fresh narrow/wide raw streams and complete identities are in `objdump-2026-09-10-boundary-repair.md`. The widened stream supplies the complete terminal bytes; semantic promotion still requires a new independent reproduction and challenge. The ownership archive's selected terminal rows have the same superseded provenance limitation. Fresh full streams, narrow/wide repairs, section-byte dumps and an audit of all ten declared windows are in `run-79357a65226f61d6a86c042d/`. Six historical stops truncate `ret imm16` after `c2`; the widened/static raw bytes establish five `c2 04 00` encodings and one `c2 08 00` encoding. This repairs terminal-byte provenance under decision `d-d4c494ba02ada278030ef473`; it does not convert the static interpretations below into live allocator-safety or accepted ABI claims. ## Recorded instruction facts * `ObservedTech::ObservedTech` at `0x008562a0` is an ECX receiver, returns that receiver in EAX, and uses plain `ret`. It installs vtable `0x00a2439c`; initializes the string rooted at `+0x0c` to `_Myres=15`, `_Mysize=0`, empty first byte; zeros `+4` (therefore both 16-bit turns), `+8`, and `+0x28`; and calls `0x00425550` for the empty-string setup. * `vector::push_back` at `0x007b7320` is ECX receiver plus one stack word (`ret 4`). It grows only when `_Mylast == _Myend`, through `0x007b5820(this, 1)`, then invokes the `0x0079a150` element-copy helper and advances `_Mylast` by exactly `0x2c`. The source-inside-vector and source-outside-vector branches both lead to this copy helper; the former recomputes the source from its pre-growth index. Thus append copies the temporary rather than adopting its string header. * The `0x0079a150` helper constructs a destination `ObservedTech`: vptr, words `+4/+6`, byte `+8`, string copy via `0x00425430`, and word `+0x28`. It is a copy construction operation, not a raw 44-byte memcpy. Independent Astra cross-check pins a **cdecl-style three-stack-argument** ABI: unused allocator argument, destination at `[ebp+0xc]`, source at `[ebp+0x10]`; plain `ret` and caller cleanup of 12 bytes. Incoming ECX is not a receiver (its initial push only allocates a local slot that is overwritten). Exact C++ template name is unnecessary for this machine boundary. * `0x007b5820` computes required capacity as old size plus its one stack-word count and uses the 1.5x growth rule when sufficient. It calls `0x007b34e0`; that reallocator calls `0x0057e590` with new element count. `0x0057e590` multiplies by `0x2c` and calls `0x00924fb6` (scalar `operator new` import thunk). Reallocation copy-constructs old elements through `0x0085e650`, calls each old element's virtual destructor slot 0 with pushed zero, then frees the old array through `0x00924faa` (scalar `operator delete` import thunk), and updates all three vector pointers. * `PlayerEvent` vector append at `0x0086c580` is ECX receiver plus one stack word (`ret 4`), grows through `0x00869500` if full, copy-constructs the element through `0x007693f0`, then advances `_Mylast` by `0x74`. The copy helper copies scalar fields and independently assigns all three strings at `+8`, `+0x24`, `+0x50` through `0x00425430`; it is not a 116-byte header copy. * `PlayerEvent` destructor body at `0x0061ae90` tests each string capacity (`+0x1c`, `+0x38`, `+0x64`) against `0x10`; for long strings it frees the buffer at `+8`, `+0x24`, `+0x50` through `0x00924faa`, then restores empty/SSO values. This establishes three independent owned-string cleanup paths in a copied event. * `0x004249a0`, called from the string assignment `0x00425430`, allocates new character storage via `0x00924fb6` and frees an existing long destination buffer through `0x00924faa` before installing the replacement pointer/size/capacity. The branch condition for long ownership is capacity `>= 0x10`; short strings stay inline. Per the independently captured PE imports, these thunks map to MSVCR100 scalar `operator delete` and scalar `operator new` respectively. ### Nested TurnEvents machine boundary * `0x00885380` is an ECX-receiver operation over the outer vector at receiver `+4`, takes one stack `int turn`, returns a `TurnEvents*` in EAX, and uses `ret 4`. It divides the outer byte span by `0x18`, scans every element, and overwrites its candidate on every `EvTurn` match. Therefore a hit returns the **last** matching bucket and performs no construction, allocation, ID update, or RNG draw. * On a miss it initializes a stack `TurnEvents` with vptr `0x00a0f07c` and zero nested-vector pointers, but does **not** initialize the temporary `EvTurn` at `+4` (`[EBP-0x20]`). It then calls outer `vector::push_back` at `0x00884cb0`; append copies that incoming stack word as its scalar `EvTurn` before deep-copying the nested vector. Get/create destroys the temporary's nested vector through `0x00629580`, only then writes the requested turn to the stored element at `_Mylast[-1]+4`, and returns the new element. The transient word is not measured randomness or necessarily nonzero; do not infer a zero default. This correction and its fresh four-window reproduction are in `run-16f8e9b6376b278c4870be09/` under decision `d-b51f3f76803e852ed250846a`. * `0x00884cb0` is ECX receiver plus one source pointer and `ret 4`; stride is `0x18`. It handles a source pointer inside its own vector separately so growth cannot invalidate the source. Both branches install the TurnEvents vptr, copy `EvTurn`, and copy-construct the nested PlayerEvent vector through `0x00779850`; this is not a 24-byte header copy. It advances outer `_Mylast` only after the nested copy call returns. * Outer full-capacity growth is `0x008841a0` -> `0x00883a60`. Capacity selection is old capacity plus half where sufficient, otherwise required size. `0x006e8f50` allocates `count * 0x18` through `0x00924fb6`. `0x0077fed0` copy-constructs every old TurnEvents, including an independent nested vector via `0x00779850`; then `0x00883a60` invokes each old TurnEvents virtual destructor with deleting flag zero, frees the old outer allocation through `0x00924faa`, and writes all three outer vector pointers. * The vtable bytes at `0x00a0f07c` identify slot zero as `0x0062e120`. That scalar-deleting destructor calls `0x00629580` on the nested vector at `+8`; with flag bit zero it does not free the inline TurnEvents object. `0x00629580` invokes every nested PlayerEvent virtual destructor in `0x74` steps, frees the nested allocation through `0x00924faa`, and zeros all three nested vector pointers. * `0x00779850` is ECX destination nested vector plus one source-vector pointer and `ret 4`. An empty source leaves three zero pointers. A nonempty source allocates `count * 0x74` through `0x0078af40` -> `0x00924fb6`, then `0x007725a0` copy-constructs each PlayerEvent through `0x007693f0`. Its unwind destroys already completed PlayerEvents; `0x00779850` then calls `0x00629580`, and outer range-copy unwind at `0x0077fed0` destroys already completed TurnEvents. Outer reallocation's landing path frees the newly allocated outer block before continuing the exception through `0x00924fbc`. These are observed cleanup edges, not a claim that allocation failure has been executed live. ### Duplicate and prune branches * `0x00825d40` receives bucket and candidate pointers as two stack words, ignores incoming ECX, and uses `ret 8`. A null bucket or empty nested vector returns zero. It scans in `0x74` steps and checks, in order: `EvAct`, `EvLoc`, all three `EvPos` floats, `EvMsg`, `EvImg`, then calls `0x0046f8c0` with the strings rooted at `EvDsc +8`. Complete fresh capture establishes that `0x0046f8c0` is a two-stack-argument, caller-cleaned string-inequality operation: first argument is the stored string, second is the candidate string, EAX/AL is one iff the strings differ, and it uses plain `ret`. `0x00825e21` therefore reaches the matched-element return only when AL is zero, i.e. when descriptions are equal. A description-only difference continues the scan. This contradicts the inherited claim that `EvDsc` was excluded; decision `d-2ff30c9f5355116bea822924` required this evidence repair after resolving surprise `s-8996365dab2cd6dc0e17bb9f`. The first element equal in every listed field returns its pointer; exhaustion returns zero. The wrapper performs no writes, allocation, destruction, ID change, event append, or RNG draw. * `0x0046f8c0` selects the candidate's inline bytes when capacity is `<0x10`, otherwise its heap pointer, and passes candidate length/data plus stored length and offset zero to `0x004236a0`. `0x004236a0` independently selects the stored string's inline/heap bytes, compares the minimum byte count through `0x00422720`, then orders unequal lengths. Equal bytes and equal lengths return zero; any byte or length difference returns nonzero, which `0x0046f8c0` normalizes to one. Thus empty/short/long combinations are covered statically without allocation or copying. The imported failure helper at IAT slot `0x009dd154` is called only when offset exceeds stored length; it is unreachable for this offset-zero call, and no failure path was executed live. Its exact imported symbol remains unresolved in this package. * `0x00879eb0` is ECX receiver plus one stack `turn` and `ret 4`. It computes cutoff `turn-50` and inspects only the leading run with `EvTurn < cutoff`. Its selected pointer is the **last** stale member. No stale member, exactly one leading stale member, or an initially empty vector returns without writes. For two or more leading stale buckets, it copies from the last stale bucket through the old end into the old beginning: each destination gets source `EvTurn`, then its nested vector is deep-assigned by `0x0077a6b0`. It destroys the trailing shifted-from TurnEvents through virtual slot zero and sets outer `_Mylast` to the end of the retained prefix. Thus it removes `stale_count-1`, deliberately retaining one stale bucket; a stale bucket after the first fresh bucket is never inspected. No outer allocation is visible in this wrapper, but nested assignment may allocate/free/copy/destroy PlayerEvents according to destination capacity. ## Ordering and boundary `RecordObservedTech` first tests for an existing matching name; name absence, not vector fullness, reaches the constructor/append sequence. The wrapper later destroys its temporary long string, so the stored element must already own an independent string allocation where applicable. Existing records retain first turn/name and update last turn/with mask; this wrapper itself does not expose a live allocation failure outcome. `EventStorage::PostEvent` constructs a temporary PlayerEvent, prunes/selects/deduplicates, and only its no-duplicate branch appends; IDs and event vector pointers remain part of the caller-level behavior documented in `findings/subsystems/events.md`. ## Remaining blockers / minimum probes 1. `0x0084ee30` remains covered only by the initial ReVa capture in this package; its full local instruction window should still be added by the independent verifier when checking default-field values. The nested construction/growth, duplicate, and prune helpers now have local raw captures. 2. Exact C++ template declarations and the runtime outcome of allocation failure remain unresolved. Static unwind edges are captured, but no live throw was induced and no allocator-safety claim is made. 3. A read-only synthetic long-string/full-capacity fixture (or a safe existing archived trace with pre/post memory) is required to falsify the static ownership interpretation. It cannot prove allocator compatibility by coherent parsing alone. 4. `EvDsc` equality participation is now statically pinned, but has not been exercised in a live same-bucket description-only fixture. Independent review must reproduce the complete helper and branch windows before this becomes accepted ABI evidence. 5. The corrected TurnEvents miss path has a fresh analyst reproduction, not independent reproduction. Full/spare outer capacity, nonempty nested copy, transient observation and failure/unwind remain unexecuted; final requested-turn publication does not certify those paths. Independent cross-check captures: `verify/results/research-callback/observed-copy-helper-objdump.txt` and `event-copy-helper-objdump.txt`. The PlayerEvent copy operation is different: ECX is the destination, one stack argument is source, EAX returns destination and `ret 4` cleans the argument. Do not reuse the ObservedTech copy-helper calling convention for PlayerEvent. Nested raw capture: `verify/results/research-completion-abi/objdump-2026-09-09-turnevents.txt`. Complete dedup/helper/callee capture: `verify/results/research-completion-abi/objdump-2026-09-09-dedup-helper.txt`. Verifier falsifiers should include: otherwise-identical records with equal versus description-only different `EvDsc`, separately using empty/short and long descriptions (static prediction: only equal descriptions deduplicate); one versus two leading stale buckets; a stale bucket after a fresh one; existing duplicate turn buckets (last-match selection); and outer spare/full capacity with nested empty/nonempty PlayerEvent vectors.