sots-re/campaign/research/record-observation-crosscheck.md

53 lines
3.4 KiB
Markdown

# Independent cross-check: RecordObservedTech
GPT-6 Astra, local instruction stream from fingerprinted `dumps/sots.exe`, 2026-09-09.
MD5 `9969481c39f4b33a8a21c48b62abee4c` exactly matches the analyst's ReVa program metadata;
the same local file's SHA-256 is `970b7de729956a53094c7eb98aba4270aee98e2fed5daf0d39e290013c90c841`.
This closes the previously reported missing ReVa/local fingerprint linkage.
Fresh disassembly: `verify/results/research-callback/record-observed-tech-objdump.txt`.
## Corrections to the first analyst pass
- The constructor/push path is **name not found**, NOT vector full capacity. The function scans
first at observer+0x274 through last at +0x278; +0x27c capacity is not read in this wrapper.
Empty list or exhausted search reaches 0x007ba274. Capacity handling belongs to 0x007b7320.
- The observer is stack argument 1 (`[ebp+8]`), not argument 2. Stack argument 2 (`[ebp+0xc]`)
supplies observation eligibility bytes and the field used to select the with-mask bit.
- Stack argument 3 is a TechDef-like object whose string starts at **object+4**. It is not a
bare string pointer: source length is object+0x14 and capacity object+0x18. The comparison uses
destination string at record+0x0c. Confirm semantic TechDef identity using the caller/schema.
## Pinned instructions / static callable boundary
Wrapper 0x007ba1a0 uses this in ECX, three stack arguments, returns with **ret 0x0c** at 0x007ba32b.
Its ECX-derived object supplies the 16-bit turn at +0x0c. The owner callback caller prepares ECX
from player+8 minus four and pushes (technology, player, player) in reverse argument order.
Before searching it requires three non-null arguments and rejects a subject with
`NPC && !RebAI` (offsets +0xfb/+0xfc, names cross-checked with the existing serializer/address
contract). It then calls 0x0057e6d0 on argument 3; a nonzero result suppresses recording.
Fresh disassembly now resolves that helper: read the technology's name string at def+4,
find its **first underscore**, and compare the suffix with **`_Root` using `_stricmp`**.
The literal is at 0x00a00040; imported comparator IAT is 0x009dd328 (MSVCR100.dll entry 115).
Thus root technologies are excluded, case-insensitively. It is not a costly master-tree query;
ECX is unused in this helper. Capture: observed-tech-exclusion-objdump.txt and
observed-tech-exclusion-literal.txt. The underscore scan has no terminator check in the binary;
valid tech-name syntax is a precondition, not evidence of safe arbitrary-string handling.
Search increments by **0x2c**, compares full names via 0x004236a0, and preserves an existing
element if found. On absence it constructs a stack temporary (0x008562a0), appends via 0x007b7320,
then operates on the newly appended element at last-0x2c:
- first/last turns are 16-bit offsets **+4/+6**;
- name assignment targets the string at **+0x0c** via 0x00425430;
- both new and existing paths update last turn **+6** and OR a bit into **+0x28**, using
subject+0x28 as the x86 shift count;
- existing-path first-turn/name stay unchanged in this wrapper.
Append is followed by conditional destruction of the TEMPORARY string when capacity >=16.
The temporary lifetime does not permit stealing its buffer into the appended element. Copy/
reallocation/destruction inside 0x007b7320 must be recovered before implementation.
These facts are static ABI/ordering evidence. They do not prove live allocation safety.