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

3.4 KiB

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.