--- name: th08-typed-re description: Generate and interpret target-pinned TH08 instruction and ABI fact packets with scripts/typed-re.py. Use for stack layout, register homes, access widths, direct calls, return cleanup, or VC7 source-shape diagnosis before strict comparison. --- # TH08 typed reconstruction Use the helper as a read-only fact extractor below the semantic decompiler layer. It consumes only the hash-attested TH08 image and repository ledgers; it never edits source, analysis databases, or tracking state. ## Generate a packet 1. Follow `$th08-re` preflight and single-session scope rules. 2. Run: ```bash python3 scripts/typed-re.py ADDRESS --compare --json \ > build/typed-re-ADDRESS.json ``` 3. Read `exact_observations` first: target bytes, stack accesses, register homes, saved registers, direct calls, absolute operands, and return cleanup. 4. Treat `inferences.compiler_recommendations` only as source-shaping probes. 5. Accept matching only through the canonical `comparison.report.result` from `scripts/compare-function.py`. If no match unit owns the address, the packet remains useful for ABI recovery, but comparison state is `not_configured`; define and review a canonical unit before claiming exactness. ## Shape source - Prefer a type or lifetime change that explains several observed instructions. - Preserve signed and unsigned narrow types when `movsx` or `movzx` requires them. - Diagnose frame differences through declaration order and real lifetimes; never add anonymous filler, inert locals, fake behavior, or ABI lies. - For a large dispatcher, derive switch-wide scratch locals from stable target EBP homes across several handlers. Reuse a real outer scalar/index home when the target does, and keep a table lookup direct when the target has no stable pointer home. Probe one slot at a time so movement of later homes remains attributable. - If a target branches once around an entire large dispatch but VC7 emits a short inverse branch followed by a near jump, test the positive condition with the whole dispatch lexically nested inside it. Equivalent early exits and gotos can produce a different branch shape. - Under the repository's VC7 `#pragma var_order` wrapper, local identifiers can affect allocation. After a stack layout is proven, treat a rename as a code-generation change and recompare it; prefer a semantic comment over a readability-only rename while tuning exact stack homes. Use the pragma only to express an already-observed lifetime/order: `Player::RegisterChain` at `0x0044C230` has the resource pointers at `EBP-4` and `EBP-0xC`, with the `g_Player` local at `EBP-8`; the natural preserve-reset-restore flow plus `#pragma var_order(primaryShtFile, player, secondaryShtFile)` exactly reproduces that target layout. It is not a license for inert locals or arbitrary stack shaping. - When a target resolves one value into a stack home and then uses that same value both to write a field and to call a setter, first test the natural chained assignment `setter = (field = ReadValue(...))`. It preserves the C++ value flow while allowing VC7 to retain one temporary; a separately named local can move fastcall parameter homes. Verify the whole function, since this is a source-shaping hypothesis rather than a byte-forcing device. - For a fixed-size slot allocator, retain the target-observed aggregate layout and express the scan as a real `for` loop over the active member. Under this VC7 `/Od` profile, `for (index = 0; index < limit; index++, slot++)` with `if (!slot->active) break;` preserves the target's initial jump, increment block, and split false/continue branches; assigning `Float2::x` and `y` separately preserves two source-address loads and stores. This is evidence for reusable allocator semantics, not permission to copy the limit, stride, offsets, or field meanings into another subsystem. TH08 Player slot allocators at `0x0044DE60..0x0044E0D8` are the exact corpus example. - When a recovered leaf accepts a pointer to a prefix of a larger aggregate, inspect its target callers before freezing the parameter type: a caller may prove the owning aggregate even when the leaf reads only a smaller prefix. Update the mapping signature and decorated match-unit symbol together, then re-run every affected comparator. `Player::FUN_0044de60` reads only x/y but is called with `Player::position` (`Float3`) by exact `0x0044D2C0`. - In a VC7 `/Od` counted loop, an early `continue` can preserve a target's explicit fall-through body and separate jump back to the increment block where a positive `if` scope emits a shorter inverse branch. Use it only when the target's condition and the skipped work establish the same semantics; `Player::FUN_0044c5b0` at `0x0044C5B0` is the exact corpus example. - If an inlined fixed-size structure-tail `memcpy` has the correct semantics, size, and `rep movsd` but schedules its count and source setup differently, probe the typed address of the first copied field instead of byte-pointer arithmetic from the parent object. This is a source-shape hypothesis, not a shortcut around full relocation replay; `ReplayManager::SaveReplay` at `0x004531F0` is the exact corpus example. - An ECX home proves only that incoming ECX was saved to a stack slot. Decide whether it is a C++ receiver or fastcall argument zero from the decorated symbol, ledger convention, and call sites; never create inert code merely to reproduce the home. - Keep target facts separate from field names, calling-convention guesses, compiler recommendations, and TH06/TH07 hypotheses. - Do not name an absolute memory operand as a standalone global from read-side evidence alone. Search write, construction, and destruction xrefs for a known global owner. If the target address is `global + member_offset`, keep that ownership in source so VC7 emits a `DIR32` relocation to the base symbol with the real addend; a zero-addend alias records the wrong layout even when it resolves to the same runtime address. RunEcl's enemy ANM pointers at `0x00F54E0C` and `0x00F54E10` are the corpus example: both are members of `g_EnemyManager`, not independent globals. Likewise, `GameManager::AddToYoukaiGauge` at `0x0043C0BB` reads `0x017D6ED4`; Player receiver reads and writes establish it as `g_Player + 0xFDC`. Modelling it as `Player::frameStop` produces the required `g_Player` DIR32 relocation with addend `0xFDC`, whereas a standalone symbol cannot faithfully replay it. - Resolve comparison destinations by each CSV header's named `address` column; the function/global ledgers place it differently from float/string ledgers. Keep attested IAT slots, import thunks, and other non-inventory symbols in `config/reccmp-relocations.csv`. Never add a fake function or global merely to make relocation replay pass. A destination being ledgered only removes an evidence gap: require exact isolated extent and full zero-difference replay before adding a match. The zwave and AnmManager unblocked batches are the corpus example (15 functions, 5,259 authored bytes, 146 relocations). - Test every source-shaping change through `$th08-matching`. - If target code ends before the next mapped function but the COFF auxiliary size continues through switch tables, compare the complete associated extent with `compare_size` while keeping `size` at the authored code extent. Report and count authored coverage from `size` only. Normalize compiler-local table symbols by relocation offset and resolved target, and replay every entry. A size error is a boundary question before it is a source-shaping instruction. The four exact AnmVm accessors at `0x0045E650..0x0045E953` are the compact corpus: 615 authored bytes plus 136 associated table bytes, all 751 compared. - Large dispatchers may own several adjacent compiler tables. Prove each table boundary from code-local pointers and the next trusted function start, keep all table-entry relocations in the canonical manifest, and require exact relocation replay over the full COFF auxiliary extent. `AnmManager::ExecuteScript` is the corpus example: `0x366D` code plus `0x1A0` bytes of 91+6+7 entries. - Do not infer a local vector type solely from matching three `f32` fields. An observed default-constructor call is a type/translation-unit fact: test the candidate type in the affected handler and retain it only if the span crosswalk improves. In particular, TH08 `RunEcl` opcode 140's `0x0040B460` construction is not reproduced by substituting SDK `D3DXVECTOR3` or the project `Float3`: both probes grew the handler by 11 bytes because they moved the vector home from the target's `EBP-0x60` to the object's `EBP-0x90`. Reconcile the dispatcher stack layout first. - When a dispatcher’s COFF extent grows or shrinks, make a read-only span crosswalk before changing source: resolve every target jump-table slot and every COFF table `DIR32` relocation to its handler start, deduplicate and sort starts in physical order, then compare adjacent target and object handler spans. This attributes a size delta to a bounded handler (including shared/default handlers) without treating Ghidra/IDA extents as compiler boundaries. It is a diagnostic fact map, not a matching claim; retain full relocation replay and canonical comparison as the acceptance gate. - When a target passes a raw float operand with `mov`/`push` but VC7 emits an `fld`/`fstp` argument shuffle, changing only a byte-tail reinterpret cast into an overlay union can leave the COFF completely unchanged. Reject that no-op probe; investigate the resolver call expression or ABI instead. TH08 `RunEcl` opcodes 34 and 39 are the corpus case. - For a dispatcher handler, do not accept a lower total COFF size as evidence that a source-shape probe is closer. Crosswalk the handler itself. In TH08 `RunEcl` opcode 39, fusing the two subtraction operands into each resolver branch shortened the whole object by 42 bytes, yet disagreed with the target's four independent resolved-value homes followed by two subtractions. Restore such a probe unless its target handler sequence improves. TH07 may suggest an expression form, but it is corroboration only; modelling TH08's operand tail as `i32[1]` rather than a byte tail did not alter this COFF. ## Improve the model The stable CLI is `scripts/typed-re.py`; implementation modules live under `scripts/typed_re/`. Changes to this automation, its rules, regressions, or skill need a dedicated reviewed commit. For a new extractor or rule, fail closed on target identity, PE mapping, ledger extent, and incomplete decoding; keep deterministic target observations out of `inferences`; add a target-pinned assertion to `--check`; then run: ```bash python3 -m compileall -q scripts/typed_re scripts/typed-re.py python3 scripts/typed-re.py --check ``` There is no VC7 library scanner yet. Do not create or claim one until SHA-pinned TH08 archives, a relocation allowlist, and comparator replay are configured; never substitute TH07 archives or rules. ## Handoff Report the address and size, exact observations used, recommendation tested, canonical comparison unit and result, and any helper/rule changes.