--- name: th08-semantic description: Replace raw TH08 object offsets, anonymous fields, protocol numbers, and absolute field views with evidence-backed C++ types and names while preserving accepted VC7 bytes and playable modern-port behavior. Use for semantic cleanup of already-authored source; do not use for new function recovery or target-linked library work. --- # TH08 semantic reconstruction Work only from the verified Japanese TH08 1.00d target and the current authored source. Read `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/RE_WORKFLOW.md`, `docs/RE_HANDOFF.md`, `docs/SEMANTIC_RECONSTRUCTION.md`, and `docs/SEMANTIC_PLAYBOOK.md` before editing. ## Preflight Run the target/tracking preflight and keep `config/claims.csv` header-only. Use `scripts/analysis/report-semantic-debt.py` as a candidate router, never as a completion metric. Select one structure and one coherent field family. Confirm every touched function in `config/mapping.csv`, `config/reccmp-functions.csv`, and `config/match-units.toml`. Establish the current accepted-unit baseline for every affected object before changing a shared declaration. ## Recover meaning Record target facts, corroboration, inference, and unknowns separately. Inspect all reads and writes, widths, signedness, bit operations, callers, strings, and canonical object ownership. TH06/TH07 and upstream names are corroboration only when TH08 evidence agrees. IDA evidence remains blocked until the active database passes `docs/IDA_MCP.md`. Treat ownership as four separate questions: storage owner, semantic owner, translation-unit/COMDAT emission owner, and production/probe/portable build owner. Evidence for one does not establish the others. Close the relevant producer, consumer, and state-transition set before changing any of them. Prefer real fields, aggregates, enums, bitfields, and member/index expressions. Preserve opaque storage when only its extent is known. Add focused `C_ASSERT` checks for relied-on sizes and offsets, remembering that layout assertions do not prove names. When one fixed storage range has several target-proven roles selected by a discriminator, a translation-unit-local typed union overlay may be safer than changing a widely included VC7 header. Anchor the single cast boundary to a real member with `offsetof`/`sizeof` assertions, keep generic initialization explicit, and use role-specific members everywhere else. This exception is for evidenced variant storage, not a way to assign meaning to unknown bytes. Do not create a new global for a view inside an existing aggregate, change field width/signedness, hide uncertainty behind a union/accessor, or combine a typed cleanup with unrelated control-flow refactoring. Serialization, instruction decoding, and platform ABI glue may be correctly byte-oriented. ## Close router-invisible protocol debt The structural debt report does not find numeric interpreter cases, stable resource/sound/effect IDs, repeated flag masks, or ABI-compatible vector casts. Audit those surfaces explicitly after field/layout cleanup: 1. Inventory every serialized opcode value and every numeric dispatch site. Include comparisons, range arithmetic, and out-of-order handlers, not only `case` labels. Give every observed value an explicit enum initializer. Preserve the handler's physical order because VC7 switch emission can depend on lexical placement. Use a neutral protocol name when the handler proves storage or control flow but not a narrower gameplay term. Do not stop after the primary opcode table: inventory operand selectors, timeline opcodes, stage/background streams, curve/mode selectors, replay synchronization flags, and other sibling dispatch tables. TH08's primary ECL opcodes were readable while all 101 operand selectors and both secondary script streams were still raw; the structural debt router did not report any of them. 2. Separate instruction selector bits from physical object flag bits. Two masks with the same numeric value are not interchangeable namespaces. Name shifts and whole-word masks when target code requires a shift/test or raw dword update; do not rewrite them as prettier bitfield access if that changes emission. 3. Name only stable numeric IDs supported by a dispatcher/table and production call roles. Resource slots may be named from load/get/release ownership; sound/effect names need target call-site behavior, with adjacent games used only as corroboration. Leave stage-script data and visually ambiguous entries numeric or neutrally named rather than guessing from one animation. 4. For representation-compatible views such as `Float3 *` and `D3DXVECTOR3 *`, prefer a macro whose expansion is the already accepted cast. A normal inline accessor can introduce a call under `/Ob0`. Keep the cast at a named boundary and validate both VC7 and the modern compiler. Once a protocol surface is complete, add a source guard that checks its exact value set, rejects numeric dispatch labels/writers on that surface, and proves that every enum member is referenced by the decoder. Keep this scoped: array indices, numeric quantities, and visually ambiguous per-file ANM script IDs are not automatically protocols and should not receive invented names merely to drive a literal count to zero. At typed call boundaries, guard every spelling of a fixed literal: a direct integer, a C-style enum cast, and `static_cast(integer)`. Match the call surface rather than banning casts globally, because casts from runtime packet fields and calculated indices remain legitimate. Cover the accepted and rejected spellings with a small regression test; otherwise a narrow regex can report zero while a different cast form remains in production source. For serialized operand families, introduce typed wire schemas only after the opcode names are stable. Pin size, offset, width, padding, and signedness with `C_ASSERT`. In a large `/Ob0` interpreter, a compile-time field-index macro is often safer than adding an `args` pointer or an accessor: derive the existing resolver's numeric index with `offsetof(Type, member) / 4`, but keep the exact resolver and bit-cast expression tree intact. Do not assume two equivalent-looking float reads emit identically. In the TH08 RunEcl readability pass, replacing `*reinterpret_cast(&raw_i)` with a generic raw-float macro shortened the function by one byte (`0x6B06` to `0x6B05`). Restoring that precise bit-cast tree inside the typed field macro recovered exact output. Enable one operand family at a time, rebuild `EclRun.obj`, and compare the complete function before moving to the next schema. ## Reconcile post-port aggregate drift When a portable-owner change breaks a previously accepted cold aggregate, classify every failure before editing source: function extent, non-relocation bytes, or relocation metadata. For relocation failures, mask the relocation fields on both sides and require zero remaining byte differences before migrating the manifest. Infer each target symbol base independently from the target field value and COFF addend, then reject the migration if it conflicts with an existing accepted base or lacks mapping/global-ledger evidence. Keep intentional nonzero addends; do not normalize them merely because a modern owner provides a more convenient spelling. An unoptimized VC7 translation unit may need a direct logical target symbol even when the portable build stores that state inside a canonical aggregate. In that case, keep the direct target declaration/storage and use a TU-local `TH08_MODERN_PORT` macro bridge so the function body has one semantic spelling. Do not replace a target-observed direct access with a non-inlined accessor. Likewise, target-adjacent globals may support one typed contiguous operation without promising adjacency in the portable linker. Use the target operation only in the VC7 build and explicit per-object operations in the portable build. Both forms must describe the same initialized state and pass their respective oracles. ## Validate and record Use `$th08-matching` for the smallest VC7 object build and replay every accepted unit in affected objects. A shared header, PCH, inline-body, layout, or object- owner change requires cold aggregate replay. If a cold aggregate replay contradicts the accepted ledger, record the exact command and generated failure set as a branch-baseline blocker. Do not publish the ledger total as a fresh exact result, broaden the semantic batch to repair unrelated units, or weaken the focused oracle that covers the edited object. Compile and link the modern target for shared-source changes. Run the Linux layout verifier and a relevant state-transition smoke when the batch changes global identity, initialization, callback state, persistence, rendering, or fixed-address ownership. VC7 exactness and Linux behavior are complementary; neither proves the English field name without the evidence record. Append an accepted batch to `docs/SEMANTIC_HISTORY.md` with addresses, evidence classes, layout assertions, exact replay results, portable result, and retained unknowns. Update `docs/RE_HANDOFF.md` only for a phase/focus/blocker change. End with tracking validation, `scripts/ci.py`, and `git diff --check`.