14 KiB
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::ObservedTechat0x008562a0is an ECX receiver, returns that receiver in EAX, and uses plainret. It installs vtable0x00a2439c; initializes the string rooted at+0x0cto_Myres=15,_Mysize=0, empty first byte; zeros+4(therefore both 16-bit turns),+8, and+0x28; and calls0x00425550for the empty-string setup.vector<ObservedTech>::push_backat0x007b7320is ECX receiver plus one stack word (ret 4). It grows only when_Mylast == _Myend, through0x007b5820(this, 1), then invokes the0x0079a150element-copy helper and advances_Mylastby exactly0x2c. 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
0x0079a150helper constructs a destinationObservedTech: vptr, words+4/+6, byte+8, string copy via0x00425430, 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]; plainretand 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. 0x007b5820computes required capacity as old size plus its one stack-word count and uses the 1.5x growth rule when sufficient. It calls0x007b34e0; that reallocator calls0x0057e590with new element count.0x0057e590multiplies by0x2cand calls0x00924fb6(scalaroperator newimport thunk). Reallocation copy-constructs old elements through0x0085e650, calls each old element's virtual destructor slot 0 with pushed zero, then frees the old array through0x00924faa(scalaroperator deleteimport thunk), and updates all three vector pointers.PlayerEventvector append at0x0086c580is ECX receiver plus one stack word (ret 4), grows through0x00869500if full, copy-constructs the element through0x007693f0, then advances_Mylastby0x74. The copy helper copies scalar fields and independently assigns all three strings at+8,+0x24,+0x50through0x00425430; it is not a 116-byte header copy.PlayerEventdestructor body at0x0061ae90tests each string capacity (+0x1c,+0x38,+0x64) against0x10; for long strings it frees the buffer at+8,+0x24,+0x50through0x00924faa, then restores empty/SSO values. This establishes three independent owned-string cleanup paths in a copied event.0x004249a0, called from the string assignment0x00425430, allocates new character storage via0x00924fb6and frees an existing long destination buffer through0x00924faabefore 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 scalaroperator deleteand scalaroperator newrespectively.
Nested TurnEvents machine boundary
0x00885380is an ECX-receiver operation over the outer vector at receiver+4, takes one stackint turn, returns aTurnEvents*in EAX, and usesret 4. It divides the outer byte span by0x18, scans every element, and overwrites its candidate on everyEvTurnmatch. 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
TurnEventswith vptr0x00a0f07cand zero nested-vector pointers, but does not initialize the temporaryEvTurnat+4([EBP-0x20]). It then calls outervector<TurnEvents>::push_backat0x00884cb0; append copies that incoming stack word as its scalarEvTurnbefore deep-copying the nested vector. Get/create destroys the temporary's nested vector through0x00629580, 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 inrun-16f8e9b6376b278c4870be09/under decisiond-b51f3f76803e852ed250846a. 0x00884cb0is ECX receiver plus one source pointer andret 4; stride is0x18. It handles a source pointer inside its own vector separately so growth cannot invalidate the source. Both branches install the TurnEvents vptr, copyEvTurn, and copy-construct the nested PlayerEvent vector through0x00779850; this is not a 24-byte header copy. It advances outer_Mylastonly 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.0x006e8f50allocatescount * 0x18through0x00924fb6.0x0077fed0copy-constructs every old TurnEvents, including an independent nested vector via0x00779850; then0x00883a60invokes each old TurnEvents virtual destructor with deleting flag zero, frees the old outer allocation through0x00924faa, and writes all three outer vector pointers. - The vtable bytes at
0x00a0f07cidentify slot zero as0x0062e120. That scalar-deleting destructor calls0x00629580on the nested vector at+8; with flag bit zero it does not free the inline TurnEvents object.0x00629580invokes every nested PlayerEvent virtual destructor in0x74steps, frees the nested allocation through0x00924faa, and zeros all three nested vector pointers. 0x00779850is ECX destination nested vector plus one source-vector pointer andret 4. An empty source leaves three zero pointers. A nonempty source allocatescount * 0x74through0x0078af40->0x00924fb6, then0x007725a0copy-constructs each PlayerEvent through0x007693f0. Its unwind destroys already completed PlayerEvents;0x00779850then calls0x00629580, and outer range-copy unwind at0x0077fed0destroys already completed TurnEvents. Outer reallocation's landing path frees the newly allocated outer block before continuing the exception through0x00924fbc. These are observed cleanup edges, not a claim that allocation failure has been executed live.
Duplicate and prune branches
0x00825d40receives bucket and candidate pointers as two stack words, ignores incoming ECX, and usesret 8. A null bucket or empty nested vector returns zero. It scans in0x74steps and checks, in order:EvAct,EvLoc, all threeEvPosfloats,EvMsg,EvImg, then calls0x0046f8c0with the strings rooted atEvDsc +8. Complete fresh capture establishes that0x0046f8c0is 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 plainret.0x00825e21therefore 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 thatEvDscwas excluded; decisiond-2ff30c9f5355116bea822924required this evidence repair after resolving surprises-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.0x0046f8c0selects the candidate's inline bytes when capacity is<0x10, otherwise its heap pointer, and passes candidate length/data plus stored length and offset zero to0x004236a0.0x004236a0independently selects the stored string's inline/heap bytes, compares the minimum byte count through0x00422720, then orders unequal lengths. Equal bytes and equal lengths return zero; any byte or length difference returns nonzero, which0x0046f8c0normalizes to one. Thus empty/short/long combinations are covered statically without allocation or copying. The imported failure helper at IAT slot0x009dd154is 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.0x00879eb0is ECX receiver plus one stackturnandret 4. It computes cutoffturn-50and inspects only the leading run withEvTurn < 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 sourceEvTurn, then its nested vector is deep-assigned by0x0077a6b0. It destroys the trailing shifted-from TurnEvents through virtual slot zero and sets outer_Mylastto the end of the retained prefix. Thus it removesstale_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
0x0084ee30remains 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.- 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.
- 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.
EvDscequality 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.- 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.