Add detailed class event processing and family comparison tools
- Enhance `extract_eusecode_flx.py` to derive class event rows with additional metadata including derived body windows and repeated template statuses. - Introduce `usecode_family_compare.py` for comparing event families, analyzing commonalities in event bodies, and generating reports on identical groups and differences. - Implement new data structures for managing class event rows and family artifact specifications. - Update output formats to include derived body information and repeated family regression checks. - Ensure robust validation of repeated family expectations against actual extracted data.
This commit is contained in:
parent
de42fd1ea1
commit
4d3c8cd81b
23 changed files with 15033 additions and 14221 deletions
|
|
@ -198,7 +198,7 @@ Second sweep through `000c` adjacent helpers — gated thunk wrappers and input/
|
|||
| `000c:9f74` | `entity_state_flag100_check_and_dispatch` | Init latch guard at `[0x6053]`; clears `[0x8c55]` on first call; checks `[ptr+0x5b]` bits `0x100` and `0x40`; three-path thunk dispatch |
|
||||
| `000c:a1ad` | `entity_state_clear_flag40_and_dispatch` | Skips if `[ptr+0x5b]` has `0x180` bits set; if `0x40` set, clears it and calls far ptr at `[0x5e82/0x5e84]`; then dispatches twice with args `(0x0b,0x10,0x1,0x0)` (record/state-key pattern) |
|
||||
| `000c:a74e` | `entity_state_dispatch_if_flag_bit2` | Tests `[ptr+0x5b]` bit `0x2`; if set pushes extra arg + ptr and dispatches via thunk |
|
||||
| `000c:84c3` | `entity_state_set_byte40_at_global_ptr` | Sets byte `[DAT_0000_6828 ptr + 0x40] = 1` then calls thunk unconditionally; enables global entity flag |
|
||||
| `000c:84c3` | `entity_state_set_byte40_at_global_ptr` | Sets byte `[g_active_dispatch_entry_farptr + 0x40] = 1` then calls thunk unconditionally; current evidence treats this as raising the shared active-entry transition/display hold byte rather than toggling an unrelated global |
|
||||
| `000c:ac55` | `entity_state_fire_if_handle_valid` | Guard: fires thunk dispatch only when `[0x6054] != -1`; no-op otherwise |
|
||||
| `000c:ac6d` | `entity_state_fire_with_args_if_handle_valid` | 3-arg variant: pushes `[BP+0xe]` (byte), `[BP+0xc]`, `[BP+0xa]`, handle `[0x6054]`, then `CALLF 0000:ffff` |
|
||||
| `000c:afa5` | `entity_state_check_field49_and_call_vfunc3c` | Checks field `[ptr+0x49]`: −1→reset to 0 return 1; 2→call `vtable[0x3c]` return 0; else thunk dispatch |
|
||||
|
|
@ -214,7 +214,7 @@ Second sweep through `000c` adjacent helpers — gated thunk wrappers and input/
|
|||
- `field49` = state-sequence index; 0=reset, 2=vtable callback, 4=triggered end
|
||||
- `field47` = keystroke-combo counter
|
||||
- `field3f` = linked data pointer (event/record reference)
|
||||
- `[0x6054]` = current entity handle; `[0x6828]` = another global entity far pointer
|
||||
- `[0x6054]` = current entity handle; `[0x6828]` = `g_active_dispatch_entry_farptr`, the shared active-dispatch entry owner whose byte `+0x40` is reused across the startup/display lane as a hold/busy token
|
||||
- Bits in `[ptr+0x5b]`: `0x1=init`, `0x2=active/event`, `0x40=pending dispatch`, `0x100=flag100`, `0x180=skip-all mask`
|
||||
|
||||
---
|
||||
|
|
@ -263,7 +263,8 @@ Globals used: `[0x6312]`=start index, `[0x6314]`=count, `[0x630e]`=palette src p
|
|||
- `entity_vm_context_save` / `entity_vm_context_load` / `entity_vm_context_destroy` / `entity_vm_context_free_buffer` (`000d:498f`, `000d:4a78`, `000d:4962`, `000d:48b6`) now pin down the lifecycle of this object family rather than leaving the whole `000d:45xx..4exx` island anonymous
|
||||
- `entity_vm_context_try_create_masked_for_entity` is now better constrained at the return-value level too: after the runtime-disable check at `0x6610` and the owner-side slot-mask test succeed, it reports two distinct success shapes. Immediate-flagged contexts (`+0x16 & 0x0008`) clear the caller output word, while object-backed contexts return the created object's low word. That makes the helper a typed bridge from gameplay entities into VM-backed object results, not only a yes/no mask probe.
|
||||
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) is now one step tighter too: the embedded seg069/070 helper is file-backed rather than abstract. Construction starts with `dos_file_handle_init` (`0009:1c00`), then uses helper vtable slot `+0x04` as the size query that drives the child `+0x10/+0x12` allocation and helper vtable slot `+0x0c` as the table-population callback for the `0x0d`-stride owner table.
|
||||
- That file-backed helper is now tighter one step deeper as well. The seg070 loops rooted at raw windows `0009:67b6` and `0009:6916` walk helper-owned record arrays at object `+0x10/+0x18`, format per-entry paths through the seg001 string helpers (`0003:e4d3` / `0003:e590`), then open, read, and close each file through `file_handle_alloc_init_and_open` (`0009:1c3a`), `dos_file_seek` (`0009:2034`), and `dos_file_close` (`0009:1e61`). That is strong evidence that `000d:7000` seeds the owner table from an indexed external file set rather than by copying one monolithic in-memory descriptor blob.
|
||||
- That file-backed helper is now tighter one step deeper as well. The seg070 loops rooted at raw windows `0009:67b6` and `0009:6916` walk helper-owned record arrays at object `+0x10/+0x18`, format per-entry paths through the seg001 string helpers (`0003:e4d3` / `0003:e590`), then open, read, and close each file through `file_handle_alloc_init_and_open` (`0009:1c3a`), `dos_file_seek` (`0009:2034`), and `dos_file_close` (`0009:1e61`). The paired `+0x18` entries are consumed as 16-bit ids passed into those path-format loops beside the far-pointer path table at `+0x10`; no object-1 or `classid + 2` arithmetic appears there, so the safest current read is slot-local file ids rather than exposed original class/object indices. That is strong evidence that `000d:7000` seeds the owner table from an indexed external file set rather than by copying one monolithic in-memory descriptor blob.
|
||||
- A final loader-side tightening from the current pass is that `0009:67b6` and `0009:6916` now read as twin entry walkers rather than one isolated path-format callback. Both windows iterate the helper-owned count at `+0x14`, index the far-pointer path table at `+0x10` and paired 16-bit id table at `+0x18`, check the source path through `0003:e669`, build formatted paths with distinct local format strings (`DS:3f2d` vs `DS:3f40`), and then reach the same file open/read/close lane. The remaining open question is not whether they are file-backed, but whether they represent two file families, two record templates, or two load phases inside the same helper class.
|
||||
- The caller-side bootstrap for that helper is now anchored too: `entity_vm_runtime_init_from_path_if_configured` (`000d:44df`) first checks the configured byte/string global at `0x65a`, builds a path through seg072 helper `0009:3600` using globals `0x6d6:0x6d8` plus `0x65a`, validates that path through `000a:500a`, then calls `entity_vm_runtime_create(0,0,path)`. This is the first verified source-argument path for `entity_vm_runtime_owner_resource_create`, and it strongly suggests the owner/resource table is loaded from an external configured file rather than from a purely in-memory descriptor blob.
|
||||
- Seg072 helper `0009:3600` is now classified more tightly as a rotating slash-aware path composer rather than a generic buffer advance helper. Its prologue cycles through five `0x50`-byte temp buffers, and its inner cases append optional string parts while inserting `\` only when adjacent path components need a separator. That narrows the two globals used by `000d:44df`: `0x65a` behaves as the configured relative runtime-owner filename/path component, while `0x6d6:0x6d8` behaves as the mutable base/resource-root path buffer that gets joined with `0x65a` before `000a:500a` validation.
|
||||
- The two still-xref-dark wrappers `0005:2c35` and `0005:2c68` are also narrower now. Their signed extra word does not participate in owner-mask selection inside `entity_vm_context_try_create_masked_for_entity`; it is forwarded into `entity_vm_context_create_from_slot_index`, stored in context field `+0x34`, and passed on to `entity_vm_slot_load_value_plus_offset`. The best current reading is therefore `offset-specialized masked context creation`, not a separate direct selector lane.
|
||||
|
|
@ -289,7 +290,8 @@ Globals used: `[0x6312]`=start index, `[0x6314]`=count, `[0x630e]`=palette src p
|
|||
- `entity_vm_state_copy` (`000c:f772`) copies that same `+0xcc..+0xd2` stream/base quartet verbatim when one mini-VM object is cloned.
|
||||
- Upstream of the setup helper, `000d:46ec` derives the source payload from the runtime owner table behind `0x6611 -> +0x1315/+0x1317`: with slot index `SI`, it walks owner table `*(owner+0x10/+0x12) + 0x0d*SI + 4`, passes that far pointer into `000c:f844`, and mirrors the resulting per-slot source into `0x39ca[slot]`.
|
||||
- This sharpens the current JELYHACK-side model rather than overturning it: the code-side producer recovered in this batch is still a generic slot-backed VM source object keyed by gameplay-entity slot selection and owner-side mask bits, not a direct hard-coded descriptor-class switch on `JELYHACK` or `JELYH2`. Combined with the extractor evidence that `JELYHACK` / `JELYH2` remain referent-only while `REE_BOOT` / `SFXTRIG` keep active `event` tags and `SURCAMEW` keeps `eventTrigger`, the better fit is still `referent anchor -> slot-backed payload chain -> neighboring event-bearing attachment`.
|
||||
- The `0x39ca` mirror question is narrower now too. Fresh windows at `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` show only global base-pointer save/restore and allocation/zeroing of the `0x39ca:0x39cc` table itself. The only verified per-slot row writer in this lane remains `entity_vm_context_create_from_slot_index` (`000d:46ec`), which writes `0x39ca[context_slot] = {source_off, source_seg}` after it derives the slot-backed payload source.
|
||||
- The `0x39ca` mirror question is now split more cleanly. Fresh windows at `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` still show only global base-pointer save/restore and allocation/zeroing of the `0x39ca:0x39cc` table itself, but two additional per-slot row writers are now verified in `000d`: `FUN_000d_7299` writes static source `DS:67f2` to `0x39ca[obj+2]` after creating a `0x44`-byte object, and `active_dispatch_entry_create_default` (`000d:761c`) writes static source `DS:6872` to `0x39ca[obj+2]` for the default active dispatch entry. `entity_vm_context_create_from_slot_index` (`000d:46ec`) remains the only confirmed owner-table-derived writer, but it is no longer the only concrete row writer overall.
|
||||
- The current pass narrows that split one step further. `entity_vm_context_create_from_slot_index` (`000d:46ec`) still derives its row from runtime owner table `(+0x10/+0x12) + 0x0d*slot + 4` before mirroring it into `0x39ca[slot]`, while `000d:7299` and `000d:761c` never touch the owner table at all in the verified windows. Instead they allocate local dispatch-entry-style objects, derive the row index from object field `+0x2`, and seed `0x39ca[row]` from fixed static sources `DS:67f2` and `DS:6872`. The safest current interpretation is therefore `owner-backed VM source mirror` versus `dispatch-entry-local static seed rows`, not three competing writers to the same semantic lane.
|
||||
- One exact numeric collision is now ruled out as unrelated noise rather than a second VM source: `000e:0953` in the animation/audio lane pushes literal `0x410` into imported `ASYLUM.27` immediately after setting the local audio-completion byte at `+0xef1`. Because `ASYLUM.DLL` is the `ASS_*` audio/media library, this does not weaken the attribution of gameplay event `0x410` to the `000d` VM/USECODE lane.
|
||||
- Current best JELYHACK reading after this pass: `JELYHACK` itself still looks like a referent-only map/object descriptor, but that no longer makes it inert. A referent-only record can still matter by supplying the referent id that populates the VM referent registry, while neighboring classes such as `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` supply the event-bearing logic attached to the same local object island.
|
||||
|
||||
|
|
@ -337,8 +339,9 @@ Conservative case identity mapping from this pass:
|
|||
|
||||
Still unresolved after this pass:
|
||||
|
||||
- Direct CALL xrefs into `FUN_000d_ebe3` are now confirmed from `animation_ctor_variant_a/b/c` at `000e:283e`, `000e:2931`, and `000e:29e4`, so the entry is no longer globally xref-dark.
|
||||
- Those constructor callsites still do not expose a new concrete wrapper-level opcode number or the direct write/read path for `[BP-0x32]`; no additional opcode id can yet be assigned uniquely beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`.
|
||||
- The animation constructor near calls at `000e:283e`, `000e:2931`, and `000e:29e4` land on a separate mis-split `000e:ebe3` region, not on `FUN_000d_ebe3`. They therefore no longer count as direct xref evidence for the `000d` dispatcher.
|
||||
- The true upstream selector/write path for `[BP-0x32]` in `FUN_000d_ebe3` is still unresolved, and no additional opcode id can yet be assigned uniquely beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`.
|
||||
- Repeated MCP-visible instruction and data-use searches still do not produce a real direct caller edge for `FUN_000d_ebe3`, `0005:2c35`, or `0005:2c68`. For now that makes the next defensible route `caller-frame / shared-consumer recovery`, not more recycled raw call searches or the retired `000a:44fd` and `000e:ebe3` hypotheses.
|
||||
|
||||
### First readable VM IR sketch (verified-only)
|
||||
|
||||
|
|
@ -461,6 +464,7 @@ The next gameplay-side wrapper pass now extends well past the three earlier seed
|
|||
#### Concrete caller/xref addendum from the next pass
|
||||
|
||||
- Direct callsites are now pinned for the simpler wrappers: `0005:0292 -> 0005:2c06`, `0005:0fee -> 0005:2cd2`, `0005:5946/59e9 -> 0005:2c9b`, and `0007:814e/822e -> 0005:2d01`.
|
||||
- The two direct `0005:2d30` callers are now role-shaped as well: `0005:5370` reaches slot `0x0f` only after `entity_class_has_flag2000` succeeds and class-word bit `0x8000` is clear, while `0005:6f47` reaches the same gate from the complementary branch where class-word bit `0x2000` is still clear before the caller continues into its larger state/update flow.
|
||||
- `0005:2c68` is no longer usable as indirect selector evidence. The `0007:e521` and `0007:e73c` instruction windows do push `0x2c68` immediately before `CALLF 000a:44fd`, but decompile now shows that value is the caller-local data pointer `DAT_0000_2c68` passed into a fatal-report helper, not an indirect call to wrapper `0005:2c68`.
|
||||
- `0005:2c35` and `0005:2c68` therefore both remain unresolved in direct caller/xref evidence, and the real selector work stays centered on the still-xref-dark upstream edge into `FUN_000d_ebe3` rather than the disproven `000a:44fd` hypothesis.
|
||||
- Net effect: the active-event ecosystem fit is reinforced by direct caller behavior and payload shapes, but final slot-to-descriptor ownership still requires real caller-role recovery for the remaining xref-dark entry points.
|
||||
|
|
|
|||
|
|
@ -220,11 +220,103 @@ Current verified behavior:
|
|||
|
||||
| Address | Name | Notes |
|
||||
|---------|------|-------|
|
||||
| `0004:60c0` | `FUN_0004_60c0` | Startup/display orchestration path: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, runs the seg137 palette and dispatch-entry helper family, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into the still-unrecovered `0004:1e00` routine. |
|
||||
| `0004:60c0` | `startup_display_transition_prepare` | Startup/display transition prepare step: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into `startup_display_transition_driver`. |
|
||||
| `0004:1e00` | `startup_display_transition_driver` | Non-return startup/display transition driver: raises the shared active-dispatch hold byte around the seg049 watch/controller lane, then clears it before the seg080 redraw and seg126 follow-up path. |
|
||||
| `000d:7600` | `active_dispatch_entry_mark_enabled` | Marks the active dispatch entry enabled |
|
||||
| `000d:760e` | `active_dispatch_entry_mark_disabled` | Marks the active dispatch entry disabled |
|
||||
| `000d:761c` | `active_dispatch_entry_create_default` | Creates the default active dispatch entry |
|
||||
|
||||
Current verified caller-side detail:
|
||||
- `startup_display_transition_prepare` now has enough exact instruction evidence to pin the seg108 lane down more tightly. Window `0004:618e..620c` calls the `0x4f38` sprite/object helpers in a stable sequence around the shared active-entry creation: seg108 `000b:1e39`, `000b:2492`, and `000b:26bd` run before `active_dispatch_entry_create_default`, and seg108 `000b:2706` runs again immediately before the handoff into `startup_display_transition_driver`.
|
||||
- The same seg108 window now shows a local bounded counter/stack contract instead of reuse of the validated caller object. `000b:26bd` increments object word `+0x196` up to `7` before calling the common local helper at `000b:2592`, and `000b:2706` reads one prior slot from `+0x186`, decrements `+0x196`, and replays the same helper before `startup_display_transition_driver` takes over.
|
||||
- The seg108 helper pair is now named too: `sprite_object_push_state_word` (`000b:26bd`) increments the bounded local stack depth at `+0x196`, stores the incoming word into the per-object stack at `+0x186`, refreshes local sprite state through `000b:2592`, and replays redraw when the object was already marked dirty; `sprite_object_pop_state_word` (`000b:2706`) returns the previous top word, decrements the same bounded depth, and reapplies the new top through that same helper. This makes the `0x4f38` lane read more like a self-contained sprite/object state stack than a reused validated caller object.
|
||||
- `startup_display_transition_driver` now has exact hold-token ordering too. Window `0004:2013..212c` raises `g_active_dispatch_entry_farptr[+0x40]`, dispatches the seg049 watch/controller object at `0x2bd8` through vtable slot `+0x2c`, runs the intervening transition call at `0004:eece`, and then clears the same shared hold byte again just before the seg080 redraw pair and seg126 follow-up.
|
||||
- Upstream caller tracing now shows that the four `0004:eece` call shapes are chosen from one startup switch-parser lane rather than from a named local phase enum. Window `0004:63c1..66fb` walks an argv-like table against a local dispatch/jump table, and the `0004:64ff..65c1` case loads globals `0x84a/0x84c/0x84e/0x850` plus scalar `0x856`; `startup_display_transition_driver` later fans those values into the `0004:2049`, `0004:20b3`, `0004:20c6`, and `0004:20fe` call variants. The other direct caller anchors at `0004:2657`, `000c:8786`, and `000c:742c` all remain inside the same startup/display presentation-handoff family, so the safest output is still tighter caller-family semantics rather than a new neutral state label.
|
||||
- The seg049 and seg108 globals are now better separated by direct decompile evidence rather than only call-window correlation. `watch_entity_controller_dispatch_if_present` confirms that `0x2bd8` is a real controller object with active vtable dispatch at slots `+0x2c` and `+0x30`, while `sprite_object_set_flag40_if_present` and `sprite_object_clear_flag40_if_present` show that `0x4f38` is a separate global object lane whose immediate local contract is only bit `0x40` in object word `+0x32`.
|
||||
- The seg127 fade-controller ownership is also one step tighter in the same lane. `transition_preentry_setup_resources` resets `0x630a` at `000c:c855`, `transition_preentry_step_script` now has a verified early gate at `000c:ca25` that yields to the fade controller whenever `0x630a` is active, and `transition_palette_fade_begin` at `000c:cdca` explicitly installs palette source/range/step state into `0x630e..0x6316`, asserts `0x630a`, and kicks one immediate fade tick.
|
||||
- Fade direction is now pinned to seg126 script-control bytes rather than the outer seg005 wrappers. Inside `transition_preentry_step_script`, control byte `0x5e` reaches `palette_fade_begin_full_down` at `000c:cb06`, while control byte `0x26` reaches `palette_fade_begin_full_up` at `000c:cd1a`; control byte `0x2a` shares the same post-fade bookkeeping path after the full-up call.
|
||||
- The upstream producer path for the remaining seg126 control bytes is now tighter too. `transition_preentry_setup_resources` composes one path from the mutable base at `0x6aa:0x6ac` plus local name buffers (`0x631c`, `0x6335`) through the seg072 slash-aware path helper `0009:3600`, opens that file through `file_handle_alloc_init_and_open`, allocates a buffer of the returned size, reads the full payload into `0x6301:0x6303`, and seeds `0x62fa/0x62fc/0x62ff/0x6305/0x630a/0x6318` before the loop starts. Current best reading is therefore `file-backed transition script/control buffer`, not locally synthesized opcodes.
|
||||
- The remaining `transition_preentry_step_script` opcodes now have stable local mechanics even though the higher-level text semantics are still open. Control byte `0x21` consumes the next script word into `SI` and advances `0x62ff` by two, which makes it the current baseline/start-position loader for later text draws. Control byte `0x40` renders one null-terminated entry from the same script buffer through renderer object `0x8c5c:0x8c5e`, while control byte `0x24` mirrors that behavior through `0x8c60:0x8c62`; both paths measure width through the renderer vtable, draw through seg088 `000a:30d7`, blit through seg080 `0009:943a`, advance `SI` by rendered width plus four, and then scan forward to the next opcode byte. Control byte `0x23` sets local completion byte `0x62fe = 1` and returns, so the outer shell exits on the next loop test instead of iterating further.
|
||||
- Secondary renderer-factory sampling keeps the `0x8c5c` / `0x8c60` split conservative. Other sampled `000a:9748` xrefs use different adjacent preset pairs such as `0x0d/0x0c` at `0007:df30/df3f` and `0x0c/0x0f` at `0008:47c9/4851`, while no sampled caller reproduced the exact `0x10/0x11` startup pair outside `transition_preentry_setup_resources`. That supports keeping these as paired preset text renderers without forcing a title/body or normal/highlight label.
|
||||
- The missing seg126 step body at `000c:ca1d` still cannot be split out safely because `create_function_by_address` collides with the existing oversized overlap namespace, so this pass preserved the recovery as a decompiler comment instead of forcing a destructive boundary repair. Current best reading is still that `000c:ca1d..cd34` is the real `transition_preentry_step_script` body and that `000c:cd35` starts the fade-tick helper.
|
||||
|
||||
---
|
||||
|
||||
## Follow-up: `0x31a2` Break/Hold Depth and Active Dispatch Ownership
|
||||
|
||||
This pass tightened the shared startup/display transition lane enough to preserve the gate semantics directly in Ghidra and to promote the active-dispatch helpers out of an isolated foothold.
|
||||
|
||||
### Verified `0x31a2` role
|
||||
|
||||
- `0008:a283` increments `0x31a2` while installing one live far-pointer slot record into the seg008 per-index table, and the paired path at `0008:a314` decrements `0x31a2` while clearing that same record.
|
||||
- `0005:453a` is now commented in Ghidra as a plain getter for the shared `0x31a2` depth word.
|
||||
- `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`) now decompiles cleanly and confirms the main transition loop runs until either local completion byte `0x62fe` becomes non-zero or `0x31a2` becomes positive.
|
||||
- The shell around `transition_preentry_step_script` is now tighter too: `000c:ca11` is the direct second exit test in the outer seg126 loop, so a positive `0x31a2` falls straight into `transition_preentry_release_resources` even when local completion byte `0x62fe` is still clear.
|
||||
- `0004:c24d` and `000c:e4d8` are now tightened as pure busy-wait edge loops on `0x31a2`, while `000c:e546` and `000c:e5c6` are the same break-depth check embedded in local presentation/cleanup loops rather than plain one-shot flag tests.
|
||||
- The blocking/waiting readers around `000c:e4d8`, `000c:e546`, and `000c:e5c6` treat `0x31a2` as an asynchronous break condition rather than a local state bit: they either busy-wait for a positive edge or abort their local presentation loop early when the depth is already positive.
|
||||
- Additional caller-side reads at `000d:9304`, `000d:b6b1`, and `000d:c0ee` all use `0x31a2 > 0` to short-circuit or advance local dispatch-entry state, which fits a shared break/hold depth better than a one-shot acknowledge flag. `dispatch_entry_kind2_tick_hold_and_maybe_dispatch` (`000d:92eb`) is still the clearest recovered dispatch-entry example: when a kind-`0x0002` entry is pending, a positive `0x31a2` lets the helper dispatch vtable slot `+0x08` even if the local `+0x40` hold byte is still asserted, after which it decrements that local byte. The newly checked `000d:b6b1` reader is narrower: it advances one local state-`5` branch only when entity byte `+0x78` is set and class/state word `+0x16` carries bit `0x4000`, then optionally runs the seg092 follow-up when the `0x45aa` gate and entity byte `+0x74a` are both active.
|
||||
|
||||
Current best neutral description: `0x31a2` is a shared asynchronous break/hold depth maintained by the seg008 install/remove path and consumed by transition/presentation code as a positive-count modal break condition.
|
||||
|
||||
### `0x6341` to `0x6828` relationship
|
||||
|
||||
- The missing seg126 function object at `000c:c63a` is now created and named `transition_preentry_setup_resources`; its body allocates the paired temporary text-renderer objects at `0x8c5c/0x8c60`, draws the preset `0x10` and `0x11` text variants, loads the file-backed buffer into `0x6301:0x6303`, and seeds the pre-entry state bytes before the main loop starts.
|
||||
- The paired helper `transition_preentry_release_resources` (`000c:c890`) still handles teardown, but its late branch at `000c:c958` also constructs the transition-local animation object at `DS:0x6341` through `animation_ctor_variant_a`, then immediately sets `g_active_dispatch_entry_farptr[+0x40] = 1` at `000c:c963`.
|
||||
- `active_dispatch_entry_create_default` (`000d:761c`) still owns the canonical `g_active_dispatch_entry_farptr` installation.
|
||||
- `dispatch_entry_kind2_tick_hold_and_maybe_dispatch` (`000d:92eb`) now makes that paired readback explicit: when a kind-`0x0002` dispatch entry is pending and either entry byte `+0x40` is already non-zero or the shared break depth `0x31a2` is positive, it dispatches vtable slot `+0x08` and then decrements `+0x40`.
|
||||
- `FUN_000d_938c` and `entity_cleanup_resources_and_dispatch` both clear `g_active_dispatch_entry_farptr[+0x40]` after their palette/presentation handoff work, which ties the active entry to the same transition-owned presentation lane rather than to an isolated constructor helper.
|
||||
- `entity_cleanup_resources_and_dispatch` is now tighter on the caller side too. Its first `0x4588` callback emit at `000d:9d5e` is confirmed as the `entity +0x12d/+0x12f` payload-pair path, while the later emit at `000d:a3b7` uses the separate `entity +0x74f/+0x751` pair. Both still sit inside the same palette/watch-controller cleanup body rather than a separate callback-only helper.
|
||||
- Caller-role alignment is now tighter across the remaining startup/display cleanup bodies. The mis-split seg126 window `000c:6176/619c -> 000c:6226` constructs only a temporary local animation payload, frees it, dispatches the seg049 watch/controller object at `0x2bd8` through vtable slot `+0x2c`, and then clears the shared owner byte `+0x40`; `FUN_000d_938c` similarly waits on two temporary palette/state entries, redraws, clears the same shared hold byte at `000d:958d`, and only then dispatches its caller object through vtable slot `+0x08`; `entity_cleanup_resources_and_dispatch` clears `g_active_dispatch_entry_farptr[+0x40]` at `000d:a1cf` only on the branch where entity byte `+0x737` is set and no temporary object remains, before falling into the same watch/controller dispatch at `000d:a1ed`. That is enough to align them as consumers of one shared presentation hold token around the seg049 lane, but still not enough to justify a single higher-level subsystem rename for `FUN_000d_938c` or the mis-split seg126 body.
|
||||
|
||||
### Follow-up: shared owner versus borrowed hold-token model
|
||||
|
||||
- `active_dispatch_entry_mark_enabled` (`000d:7600`) and `active_dispatch_entry_mark_disabled` (`000d:760e`) are now verified as tiny wrappers that only write the shared owner byte `g_active_dispatch_entry_farptr[+0x40] = 1/0`; they do not install or replace the owner object.
|
||||
- `entity_dispatch_entry_init_runtime_state` (`000d:7e00`) now tightens that ownership split further. When it builds a runtime-state dispatch entry, it copies the current shared owner byte at `g_active_dispatch_entry_farptr[+0x40]` into the new entry's local byte `+0x40`; if the new entry stays inactive while a shared owner exists, it raises the shared owner's `+0x40` byte to `1` instead of replacing the owner pointer.
|
||||
- The paired destructor `entity_dispatch_entry_release_runtime_state` (`000d:8078`) clears `g_active_dispatch_entry_farptr[+0x40]` when the runtime-state entry was marked as owner-propagating (`+0x41 != 0`) or when the entry's local hold byte was never asserted. This matches borrowed hold-state propagation, not separate owner creation.
|
||||
- `startup_display_transition_driver` now provides the clearest caller-side proof in seg005: it raises `g_active_dispatch_entry_farptr[+0x40]` at `0004:2013` before the seg049 watch/camera path and the `0004:eece` transition call, and clears that same byte again at `0004:2128` before the seg080 display update pair, sprite redraw, and seg126 follow-up thunk `000c:82f9`.
|
||||
- The still-mis-split seg126 window around `000c:6176/619c` also fits the same model. It makes one mode-dependent `animation_ctor_variant_a` call on a temporary local object, frees that temporary object, reloads the palette, dispatches the `0x2bd8` watch/camera controller through vtable slot `+0x2c`, and later clears `g_active_dispatch_entry_farptr[+0x40]` at `000c:6226`. No canonical owner installation is visible in that body.
|
||||
- The thin seg005 wrappers at `0005:3c36` and `0005:3c5b` are now confirmed as pure `animation_ctor_variant_a` preset shims. Combined with the seg126 windows above, current best reading is: `DS:0x6341` and the other constructor callsites build transition-local or display-local animation payloads, while `0x6828` remains the shared active-dispatch owner installed elsewhere.
|
||||
|
||||
Current best model: `g_active_dispatch_entry_farptr` is a shared owner installed by `active_dispatch_entry_create_default`, and the startup/display transition lane mostly borrows and propagates the owner's byte `+0x40` as a hold/busy token while palette/runtime-state helpers run. The remaining open problem is the exact state/object label behind the seg049 watch/camera path, the seg108 sprite/object lane, and the cleanup branches that consume the same token.
|
||||
|
||||
### Current batch: presentation handoff family versus single-owner hypothesis
|
||||
|
||||
- The exact late sequencing now supports one stricter neutral read: these bodies behave like a shared startup/display presentation handoff family, but not like one single owner-object family. In `startup_display_transition_prepare`, the validated caller object (vtable `+0x0c`), the seg108 `0x4f38` lane, and the seg049 `0x2bd8` watch/controller lane stay separated by direct instruction windows rather than collapsing into one reused object path.
|
||||
- `transition_preentry_setup_resources` also tightened the paired renderer role one step further. Window `000c:c659..c6ab` allocates renderer presets `0x10` and `0x11` through seg099 `000a:9748`, stores them at `0x8c5c:0x8c5e` and `0x8c60:0x8c62`, and immediately draws the same seed text buffer `DS:0x631a` at `(0x0a,0x0a)` through both objects. That makes the pair look like two preset text-render variants inside the same temporary presentation lane, not two separate owner objects.
|
||||
- The shared hold-token consumers now line up more exactly than before. `startup_display_transition_driver` raises `g_active_dispatch_entry_farptr[+0x40]` before the seg049 `+0x2c` dispatch and clears it again before the redraw/follow-up path; `transition_preentry_release_resources` tears down the paired renderers and script buffer, and only on its late completion branch builds local `DS:0x6341` then raises the same shared byte; the mis-split `000c:6176/619c -> 000c:6226` body frees its temporary local animation object, reloads the palette, dispatches `0x2bd8`, and only then clears the shared byte; `FUN_000d_938c` waits on two temporary palette/state entries, redraws, clears the shared byte, and only then dispatches caller vtable `+0x08`; `entity_cleanup_resources_and_dispatch` clears the shared byte only on the late `+0x737` cleanup branch immediately before the same `0x2bd8` dispatch.
|
||||
- The seg049 controller lane is also slightly tighter locally. `watch_entity_controller_create_global` (`0007:ba00`) delegates to `watch_entity_controller_create` (`0007:ba45`), which stamps type `0x2c2b`, stores the global object at `0x2bd8`, and seeds static row `DS:0x2be4` into `0x39ca[obj+2]`; the common `watch_entity_controller_dispatch_if_present` wrapper (`0007:ba13`) then runs both vtable slots `+0x2c` and `+0x30`. That still supports a real controller object, but not a strong enough state label to rename the wider family.
|
||||
- The seg108 lane is one step tighter in the same pass. `sprite_redraw_if_needed` (`000b:2492`) remains the redraw-facing helper, while the newly named `sprite_object_push_state_word` / `sprite_object_pop_state_word` pair show that prepare-time use of `0x4f38` is bracketed by a bounded per-object state-word stack at `+0x186/+0x196` rather than by reuse of the validated caller object or the seg049 controller.
|
||||
|
||||
Current safest naming conclusion from this batch: keep the existing concrete function names, treat `FUN_000d_938c` and the related seg126/seg138 callers as one shared startup/display presentation handoff family, and defer any stronger single-state or single-owner rename until a caller-side state discriminator appears.
|
||||
|
||||
This is enough to treat seg136 as a shared active-dispatch owner/hold-state controller and seg138 as a real cleanup/presentation caller family, even though the final subsystem name is still open.
|
||||
|
||||
### Current batch: exact edge waits, interleaved handoff, and broader sprite-stack reuse
|
||||
|
||||
- The remaining pure `0x31a2` edge waits are now exact rather than inferred. `0004:c24d` is a two-phase wait that first spins while the shared break/hold depth is non-zero and then spins until it becomes positive again before continuing, while `000c:e4d8` is the simpler positive-edge gate that only waits for `0x31a2 > 0` and immediately returns into the local presentation path.
|
||||
- `FUN_000d_938c` is now slightly tighter on sequencing even though it still does not justify a rename. The first scratch-palette runtime-state entry (`kind 0x3c`) is only built on the branch where global `0x68e6` is not already in the `0x13:0x0008` mode or entity byte `+0x33` is clear; after that wait completes, the helper clears the seg049 controller bit and dispatches `0x2bd8` through vtable slot `+0x2c`, performs one rectangle/display sync, and only then conditionally builds the current-palette runtime-state entry (`kind 0x14`) before the final redraw, shared hold-byte clear, and caller-object vtable `+0x08` dispatch. That keeps the body inside the same presentation-handoff family while making it less plausible as a single-owner constructor.
|
||||
- Additional seg108 push/pop callsites now show that the `0x4f38` lane is reused outside the startup prepare shell. Windows `000b:9bb8/9bda` and `000b:9c3c/9c6e` bracket transient seg101 presentation helpers with the same `sprite_object_push_state_word` / `sprite_object_pop_state_word` pair on global sprite object `0x5e82:0x5e84`, while `000c:831f`, `000c:8845`, `000c:8909`, and `000c:a05f` pop that same state stack during later UI or object-cleanup flows. This strengthens the neutral reading that `0x4f38` is a generic sprite-object state stack, not the validated prepare-time caller object and not the `0x2bd8` watch/controller object.
|
||||
|
||||
### Current batch: shared hold-token ownership closure
|
||||
|
||||
- The highest-value remaining ownership question in the startup/display lane is now narrow enough to close without a speculative rename. `active_dispatch_entry_create_default` remains the canonical installer for `g_active_dispatch_entry_farptr`, while the later seg005/seg126/seg138 bodies only borrow or propagate the shared owner byte `+0x40` as a transition/presentation hold token.
|
||||
- The set/clear sites now line up as one borrowed-hold family instead of competing owner installs. `startup_display_transition_driver` raises the shared byte at `0004:2013` before the seg049 controller dispatch and clears it at `0004:2128`; `transition_preentry_release_resources` raises it only on the late completion branch at `000c:c963` after building temporary `DS:0x6341`; `FUN_000d_938c` clears it at `000d:958d` after both temporary palette/state waits and redraw; `entity_cleanup_resources_and_dispatch` clears it at `000d:a1cf` only on the late cleanup branch immediately before the same seg049 controller dispatch.
|
||||
- The seg049 and seg108 lanes stay separate in the exact places that matter for ownership. `watch_entity_controller_dispatch_if_present` (`0007:ba13`) confirms `0x2bd8` is a real controller object dispatched through vtable slots `+0x2c/+0x30`, while `sprite_object_clear_flag40_if_present` / `sprite_object_set_flag40_if_present` (`000b:2b08` / `000b:2b20`) only toggle bit `0x40` in the separate global sprite/object at `0x4f38 + 0x32`.
|
||||
- The owner/borrow split also remains visible inside the dispatch-entry helpers. `entity_dispatch_entry_init_runtime_state` copies the shared owner byte into new runtime-state entries and re-raises the owner's `+0x40` byte when needed, which matches propagation of borrowed hold state rather than transfer of owner identity.
|
||||
|
||||
Current best neutral conclusion from this pass: the shared `g_active_dispatch_entry_farptr[+0x40]` byte is a startup/display presentation hold token borrowed across the seg049 controller lane and later cleanup/handoff bodies; the seg108 `0x4f38` lane is a separate local sprite-object state stack with its own bit-`0x40` contract, not the owner of the shared active-dispatch token.
|
||||
|
||||
### Current batch: seg126 control-stream producer tightening and completed `0x31a2` read classes
|
||||
|
||||
- The higher-level seg126 control-byte producer is now tighter without breaking the conservative file-backed model. `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`) still has no data/bytecode arguments and is only reached from the local wrappers at `000c:7427` and `000c:0d0d`; both wrappers only stage the surrounding presentation lane before entering the seg126 loop, and neither injects script bytes or a script pointer.
|
||||
- `transition_preentry_setup_resources` (`000c:c63a`) remains the only verified source of the consumed bytes. It copies the shared mutable base path from `0x6aa:0x6ac`, composes local filenames through the slash-aware seg072 helper `0009:3600` using local buffers `0x631c`, `0x6323`, `0x632c`, and `0x6335`, opens the resulting file through the seg070 file-handle lane, allocates a buffer of the returned size, and reads the full payload into `0x6301:0x6303` before seeding `0x62fa/0x62fc/0x62ff/0x6305/0x630a/0x6318`.
|
||||
- Neighboring seg126 code now supports the same selector-path reading. Window `000c:b018..b03d` also reloads the same shared base path from `0x6aa:0x6ac` and composes a sibling local filename through `0009:3600` using `0x621c/0x6223`, which makes the startup/display lane look more like a family of file-selected transition assets than a local script-byte emitter.
|
||||
- The upstream `0x6aa:0x6ac` question is now narrow enough to close as an earlier inherited path lane rather than an in-scope seg126 producer. Literal-address instruction search still finds no store into `0x6aa` or `0x6ac`; the seg004 parser window only mutates the first byte of the pointed buffer at `0004:0ccd` / `0004:0cd8`, while the same parser explicitly installs the sibling root `0x6ae:0x6b0` from parsed input at `0004:0d28..0d2c`. Current best read is therefore: `0x6aa:0x6ac` already points at a mutable external/default base-path buffer before the seg126 startup/display family begins composing filenames on top of it.
|
||||
- The neighboring seg126 helper family sharpens that close further. Window `000c:afa5..b152` keys object field `+0x49` through local values `0`, `1`, and `4`, composes three sibling filenames from the shared stem buffer `0x621c` plus suffix buffers `0x6223`, `0x622d`, and `0x6237`, loads the selected file into object `+0x520` through seg002 `0004:0098`, then runs the same display/update chain; wrapper `000c:b153..b25f` increments or decrements `+0x49` on selected event codes and re-enters that same loader. This makes the nearby seg126 lane look like a local three-way transition-asset family selector layered on top of the inherited shared base path.
|
||||
- The overlap check does not force repair yet. `analyze_function_boundaries` still reports `000c:ca1d` as a plausible function entry and `000c:cd53` as the next clean function, while the oversized overlap rooted at `000c:db68` still pollutes the namespace. That overlap no longer blocks byte-behavior or read-site classification, but it still blocks clean function recovery for `transition_preentry_step_script`.
|
||||
- The in-scope `0x31a2` readers are now classed cleanly by role. `0004:c24d` and `000c:e4d8` are edge waits; `000c:ca11` is the seg126 modal-break exit; `000c:e546`, `000c:e5c6`, and `000d:c0ee` are cleanup-abort exits; `000d:9304` and `000d:b6b1` are deferred dispatch/state-advance gates.
|
||||
- Two remaining `0x31a2` reads stay outside that presentation classification set. `0005:453d` is only a plain getter wrapper for the shared depth word, and `0008:5149` is a seg008 internal/accounting-side read that adds the current depth to another local count before tripping a `>= 0x10` capacity flag.
|
||||
|
||||
---
|
||||
|
||||
## Follow-up: `0x4588` Object-Role Evidence
|
||||
|
|
@ -282,7 +374,9 @@ The next ScummVM-guided validation step now confirms that the sampled owner-load
|
|||
- Scanning with the previously noted ScummVM-style `(base_offset + 19) / 6` interpretation overruns into inline payload/name bytes on these owner-loaded records, so the local sample set does not support that exact event-count formula as written.
|
||||
- The best current arithmetic fit is now tighter: ScummVM's decremented `base_offset` is also used as the live code-stream base in `uc_machine.cpp`, so the local owner-loaded records fit best if bytes `8..11` are the first code-byte offset and event-count derivation is `(base_offset - 19) / 6`, which is exactly equivalent here to `(raw_u32_at_8_11 - 20) / 6`.
|
||||
- Current `000d` loader evidence does not point to a header rewrite before VM consumption. `entity_vm_runtime_init_from_path_if_configured` (`000d:44df`) only builds the external path and creates the runtime, `entity_vm_runtime_create` (`000d:4c99`) only installs the helper returned by `000d:7000`, `entity_vm_runtime_owner_resource_create` (`000d:7000`) only allocates the child owner table and fills it through helper vtable `+0x0c`, and `entity_vm_context_create_from_slot_index` (`000d:46ec`) directly reads slot-backed source data from that owner table. No local step is yet verified as rewriting the sampled class headers.
|
||||
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) still does not expose a direct binary-side class-name lookup or explicit `classid + 2` arithmetic. What it does expose is an indexed file-set loader contract: helper-owned count at `+0x14`, far-pointer table at `+0x10`, paired per-entry word table at `+0x18`, vtable `+0x04` size query, and vtable `+0x0c` materialization of the `0x0d`-stride owner records later consumed by `entity_vm_context_create_from_slot_index`.
|
||||
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) still does not expose a direct binary-side class-name lookup or explicit `classid + 2` arithmetic. What it does expose is an indexed file-set loader contract: helper-owned count at `+0x14`, far-pointer table at `+0x10`, paired per-entry word table at `+0x18`, vtable `+0x04` size query, and vtable `+0x0c` materialization of the `0x0d`-stride owner records later consumed by `entity_vm_context_create_from_slot_index`. The current pass also makes the helper shape slightly more concrete: the two raw seg070 windows at `0009:67b6` and `0009:6916` are twin per-entry path/read loops with distinct format strings (`DS:3f2d` and `DS:3f40`) but the same `+0x10/+0x18` indexing and file open/read/close lane, which is better evidence for a multi-table or multi-phase external loader than for direct in-memory descriptor iteration.
|
||||
- The signed slot-offset lane used by the still-xref-dark wrappers `0005:2c35` / `0005:2c68` is also no longer confined to `entity_vm_context_create_from_slot_index` (`000d:46ec`). Inside `entity_vm_runtime_create`, the pre-entry body at `000d:4c25..4c90` reloads object fields `+0x32/+0x34` through `entity_vm_slot_load_value_plus_offset` (`000d:5572`), stores that returned pair into object fields `+0x10c/+0x10e`, and also caches the owner-source far pointer at `+0x117/+0x119`. The paired save path at `000d:49ec` then serializes `+0x10c` through seg070 `0009:2034`, which makes the slot-plus-offset pair a persisted runtime/dispatch state lane rather than a transient wrapper-only argument.
|
||||
- Additional `0x39ca` consumers are now classified more cleanly. Beyond the already-known static seeds at `000d:7299 -> DS:67f2` and `000d:761c -> DS:6872`, the constructor-like windows at `000d:929a` and `000d:963c` seed rows `DS:68ec` and `DS:68f5` respectively before enabling local timer/dispatch behavior. Those writes behave like dispatch-entry-local static seed rows, not owner-table mirrors. Separately, `FUN_000d_938c` reads temporary dispatch-entry fields `+0x32/+0x34` at `000d:9449..9468` and `000d:9547..9566` only as a wait/poll condition on the scratch-palette (`kind 0x3c`) and current-palette (`kind 0x14`) entries it creates, which further separates active dispatch-entry state from the owner-backed `0x39ca[slot] = {source_off, source_seg}` rows written by `000d:46ec`.
|
||||
- Safe event-label correlation remains intentionally narrow after this pass. The sampled low slot ids are now concrete, but none of them yet have a verified binary-side behavior match strong enough to promote a ScummVM label like `look`, `use`, or `cachein`.
|
||||
|
||||
### Conservative parser rule from this batch
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ A small helper cluster in the raw `000e:` area implements a fixed-size CRLF reco
|
|||
- `table_end = 0x6090`, which matches the first non-zero payload offset
|
||||
- `403` non-zero entries in the current file
|
||||
- `tools/extract_eusecode_flx.py` now parses the full validated table and emits all `403` non-zero entries under `USECODE/EUSECODE_extracted/`, including `entry_index.tsv`, `descriptor_index.tsv`, `descriptor_neighborhoods.tsv`, `summary.json`, per-chunk `.bin`, and `.strings.txt` sidecars.
|
||||
- The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs: `class_layout_index.tsv` records `object_index`, `class_id`, the raw bytes-`8..11` field, derived `code_base_minus_one`, and `conservative_event_count`, while `class_event_index.tsv` expands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, and raw code-offset dwords.
|
||||
- The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs: `class_layout_index.tsv` records `object_index`, `class_id`, the raw bytes-`8..11` field, derived `code_base_minus_one`, and `conservative_event_count`, while `class_event_index.tsv` expands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, raw code-offset dwords, derived body-window columns, and conservative repeated-template status tags for the verified repeated families.
|
||||
- The extractor now emits one concrete generated per-class decompile artifact for the cleanest repeated lane too: `boot_family_decompile.md` / `.tsv` render the five `_BOOT` classes slot-by-slot with raw row bytes, derived body windows, repeated-template status, and stable body digests.
|
||||
- The generated reports now expose lightweight descriptor summaries (`primary_label`, `field_names`, `field_tags`) so the object lane can be searched by field grammar instead of only by raw names.
|
||||
- The extracted data now separates into at least two lanes:
|
||||
- text-heavy records that fit the `000e:` CRLF parser model, such as `DATALINK` mission/objective text and `TEXTFIL1` message banks
|
||||
|
|
@ -129,6 +130,19 @@ A small helper cluster in the raw `000e:` area implements a fixed-size CRLF reco
|
|||
- The environmental hazard lane is now similarly constrained. `environmental_family_compare.tsv` shows that `FLAMEBOX` and `STEAMBOX` are close structural siblings with the same active-event backbone (`referent,event,<hazard>,<hazard2>,direction,count`) and matching `24:0A02 / 24:FC02 / 24:FE02` object-link pattern, while `NOSTRIL` is a smaller fire-specific variant that keeps the active `event` plus dual fire references and count fields but drops the direction/newType side.
|
||||
- Their neighborhoods are different enough to matter: `environmental_event_graph.md` shows `FLAMEBOX` embedded among vent/door/bridge/copy records, `NOSTRIL` among flame/pad/desk/blaster/keypad records, and `STEAMBOX` among bounce/hover/fade/steam/flame box records. So this looks like one hazard-event descriptor family reused across distinct local object islands rather than one single environmental mega-cluster.
|
||||
- The callback lane is tighter too. `callback_trigger_compare.tsv` confirms that `SURCAMNS` and `SURCAMEW` are effectively the same callback-trigger template: identical field set (`referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun`) and identical tag grammar except for the `therma` slot offset (`24:F102` vs `24:F602`). That keeps the `eventTrigger` split credible as a true callback/attachment lane rather than only a spelling variation on active `event` carriers.
|
||||
- Mining the new `class_layout_index.tsv` / `class_event_index.tsv` outputs now gives a first small safe set of repeated non-zero slot patterns:
|
||||
- `JELYHACK` and `JELYH2` are exact referent-anchor twins at the event-table level too: both have only slot `0x01` non-zero, with the same row `0x002A / 0x00000001`.
|
||||
- The five `_BOOT` event cores (`AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `REE_BOOT`, `VAR_BOOT`) all share the same three-slot pattern `0x0A / 0x0F / 0x10`. The clearest exact repeated row is slot `0x10`, where all five use `raw_event_entry_word = 0x003B` with class-specific code offsets.
|
||||
- `SURCAMNS` and `SURCAMEW` share one exact five-slot callback pattern `0x01 / 0x0A / 0x20 / 0x21 / 0x22`, including the same `0x0A = 0x00D1 / 0x00000001` anchor row and the same `0x22` event-table word `0x01A3`.
|
||||
- `FLAMEBOX`, `NOSTRIL`, and `STEAMBOX` share one environmental-event pattern `0x0A / 0x20 / 0x21`, which is enough to treat the higher slots as real repeated structure even though the exact row values differ by class.
|
||||
- `EVENT` and `SFXTRIG` both participate in the wide `0x0A` lane, but that lane is broad enough that the slot number is currently more trustworthy than the ScummVM label attached to it.
|
||||
- The next body-window pass now confirms that repeated slot rows are usually near-templates rather than clones. Using `body_start = code_base_minus_one + raw_code_offset` and the next non-zero slot offset or chunk EOF as the body end:
|
||||
- `JELYHACK` and `JELYH2` slot `0x01` are both `42` bytes long with a shared `10`-byte prefix and `17`-byte suffix, but are not byte-identical.
|
||||
- `_BOOT` slot `0x10` is a clean short-template lane: all five bodies are exactly `59` bytes long, share the same first `5` bytes and last `17` bytes, but each has a distinct digest.
|
||||
- `_BOOT` slots `0x0A` and `0x0F` are larger variants of the same pattern: shared suffix-heavy structure, class-local middles, no exact clones.
|
||||
- `SURCAMNS` and `SURCAMEW` slots `0x20` and `0x22` are same-length near-templates (`698` and `419` bytes respectively), while slot `0x21` diverges more strongly (`1801` vs `1621` bytes) even though it still keeps a common tail.
|
||||
- That makes the current best human-readable script model more precise: preserve repeated-family status and exact row bytes, but record byte-identity as a separate property so “same slot template” does not get mistaken for “same compiled body.”
|
||||
- That pattern pass materially improves what a decompiled USECODE script can look like right now. The honest current form is not a pretty source language; it is a reversible descriptor-plus-event-table rendering with raw slot ids, raw event-entry words, raw code offsets, and optional ScummVM labels marked as hints only. The concrete examples now live in `docs/usecode-roundtrip-ir.md` and are grounded in `readable_script_ir.md`, `readable_descriptor_templates.md`, and `runtime_descriptor_family_rankings.md`.
|
||||
- The first runtime-side follow-through on those descriptor gains is now a little tighter too. Instruction search around `000d:ebe3` confirms one fixed sequenced VM/opcode driver body, not just a vague constructor helper: it calls `000d:177c`, `000d:1acb`, `000d:0988`, the internal `000d:22bc` link-matrix block, then `000d:1d4a` and `000d:2104` in order. The key negative result is just as useful: `000d:ec31` is only the internal `CALL 000d:22bc` site inside that body, not a standalone function entry.
|
||||
- Ghidra now carries that as a conservative disassembly comment at `000d:ebe3`. That is still short of a safe rename, but it does promote the lane from “suspected constructor chain” to “verified ordered opcode/handler sequence,” which is the clearest current bridge from the descriptor-side event families back into the `000d` VM/object runtime.
|
||||
|
||||
|
|
@ -191,17 +205,17 @@ All three constructor variants (`000e:2777`, `000e:2860`, `000e:2969`) follow th
|
|||
|
||||
1. Call `FUN_000e_e935` (allocator — produces garbled 11KB decompile, not renamed)
|
||||
2. Set fields `+0xb4` through `+0xc2` on the result
|
||||
3. Call `000d:ebe3` directly (confirmed CALL sites at `000e:283e`, `000e:2931`, `000e:29e4`; multi-step chain initializer: calls `177c`, `1acb`, `0988`, `22bc`, `1d4a`, `2104` in sequence)
|
||||
3. Call near target `000e:ebe3` directly (confirmed CALL sites at `000e:283e`, `000e:2931`, `000e:29e4`; this is a separate mis-split `000e` region, not `FUN_000d_ebe3`)
|
||||
4. Call `assert_alive_sentinel` (assertion: checks `+0xd4 != -1`)
|
||||
5. Call `func_0x000eec83`
|
||||
|
||||
The chain at `000d:ebe3` steps through VM opcode handlers (`000d:177c`, `000d:1acb`, `000d:0988`) that operate on a bytecode VM object with stack pointer at `+0xcc` (decremented by 2 per push) and segment base at `+0xce`.
|
||||
The old assumption that these constructor calls fed the `000d` VM sequencer is now retired. Raw instruction search shows the direct near calls land on `000e:ebe3`, whose current body is still mis-split/garbled and cannot yet be tied to the `000d:177c` / `000d:1acb` / `000d:0988` / `000d:22bc` / `000d:2104` chain.
|
||||
|
||||
The constructor-side field setup before that sequencer is now slightly tighter too:
|
||||
|
||||
- variants A and B both set `+0xc0 = 1` before the direct `000d:ebe3` call and derive `+0xc2` from `DS:0x604e`
|
||||
- variant C instead sets `+0xc0 = 0`, `+0xc2 = 1`, and `+0x4c = 0x000d` before the same sequencer call
|
||||
- these direct xrefs make `000d:ebe3` a constructor-side animation sequencer rather than a globally xref-dark dispatcher, but they still do not expose any new wrapper-level opcode number beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`
|
||||
- variants A and B both set `+0xc0 = 1` before the direct `000e:ebe3` call and derive `+0xc2` from `DS:0x604e`
|
||||
- variant C instead sets `+0xc0 = 0`, `+0xc2 = 1`, and `+0x4c = 0x000d` before the same near-call lane
|
||||
- this remains useful for the animation subsystem, but it no longer counts as upstream xref evidence for `FUN_000d_ebe3`; the true selector/write path into the `000d` dispatcher is still unresolved
|
||||
|
||||
### Constructor variant renames
|
||||
|
||||
|
|
|
|||
|
|
@ -234,6 +234,315 @@ For near-term local RE and tooling:
|
|||
- Treat `JELYHACK` and `JELYH2` as referent-anchor classes, not standalone event records.
|
||||
- Treat `SURCAMNS` and `SURCAMEW` as callback/eventTrigger holders, not proven active-event cores.
|
||||
|
||||
## Repeated Slot Patterns Safe To Reuse Now
|
||||
|
||||
The latest pass over `class_layout_index.tsv` and `class_event_index.tsv` adds a small set of repeatable slot patterns that are safe enough to carry into decompiler output.
|
||||
|
||||
What is authoritative here:
|
||||
|
||||
- whether a class has a non-zero slot entry at a given slot id
|
||||
- the raw `u16` event word for that slot
|
||||
- the raw `u32` code offset for that slot
|
||||
- repeated slot-set structure across several classes
|
||||
|
||||
What is still hint-level only:
|
||||
|
||||
- the ScummVM event-name labels for slots `0x00..0x1f`
|
||||
- any mapping from one repeated slot directly to one recovered `000d` opcode family
|
||||
- any claim that one repeated slot family is already tied to one exact gameplay subsystem in the DOS binary
|
||||
|
||||
Current small safe candidate sets:
|
||||
|
||||
| Family | Classes | Non-zero slots | Safe implication |
|
||||
|---|---|---|---|
|
||||
| referent-anchor twin | `JELYHACK`, `JELYH2` | `0x01` only | these are structurally anchor-only classes, not active event hubs |
|
||||
| boot-event-core | `AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `REE_BOOT`, `VAR_BOOT` | `0x0A`, `0x0F`, `0x10` | one reusable three-slot active-event core template |
|
||||
| callback-eventtrigger | `SURCAMNS`, `SURCAMEW` | `0x01`, `0x0A`, `0x20`, `0x21`, `0x22` | one shared callback-oriented multi-slot template |
|
||||
| environmental-event | `FLAMEBOX`, `NOSTRIL`, `STEAMBOX` | `0x0A`, `0x20`, `0x21` | one shared hazard/event template with two extra high slots |
|
||||
| broad active-event lane | `EVENT`, `SFXTRIG`, and several non-island classes | `0x0A` only | slot `0x0A` is widespread enough to treat as a real repeated event slot, but too broad to over-specialize |
|
||||
|
||||
Concrete repeated evidence worth preserving in IR:
|
||||
|
||||
- `JELYHACK` and `JELYH2` both carry only slot `0x01` with the exact same row: `raw_event_entry_word = 0x002A`, `raw_code_offset = 0x00000001`.
|
||||
- The five `_BOOT` cores all share slot `0x10` with the exact same `raw_event_entry_word = 0x003B`, while the `raw_code_offset` varies by class (`0x0000045c` on `COR_BOOT`, `0x0000048b` on `AND_BOOT`, `0x00000522` on `BRO_BOOT`, `0x000004df` on `VAR_BOOT`, `0x000005a8` on `REE_BOOT`). That is a good example of repeated structure without identical bodies.
|
||||
- `SURCAMNS` and `SURCAMEW` share the same five-slot layout and the same low/high anchor rows (`0x0A = 0x00D1/0x00000001`, `0x22 = 0x01A3/...`), but differ in the middle high-slot offsets. That looks like one shared callback template with instance-specific bodies, not two unrelated classes.
|
||||
- `FLAMEBOX`, `NOSTRIL`, and `STEAMBOX` all share one `0x0A` low slot plus two extra high slots `0x20` and `0x21`. Their exact words differ, so the safe reading is shared layout, not identical compiled behavior.
|
||||
- `EVENT` and `SFXTRIG` both participate in the wide `0x0A` lane, but that family is broad enough that the slot number is more trustworthy than the ScummVM name hint.
|
||||
|
||||
## Byte-Level Body Comparison Rules And Results
|
||||
|
||||
The next step after repeated row mining is to derive the chunk-local body window for each non-zero slot and compare the actual bytes instead of only the 6-byte event-table row.
|
||||
|
||||
Current conservative body-window rule:
|
||||
|
||||
- `body_start = code_base_minus_one + raw_code_offset`
|
||||
- `body_end = code_base_minus_one + next_non_zero_raw_code_offset` in the same class, or chunk EOF when there is no later non-zero slot
|
||||
- this keeps the representation reversible because it is computed only from preserved header and event-table fields plus the raw chunk bytes
|
||||
|
||||
This rule is now carried directly by the extractor outputs instead of living only in notes:
|
||||
|
||||
- `USECODE/EUSECODE_extracted/class_event_index.tsv` now emits `derived_body_start`, `derived_body_end`, `derived_body_length`, and conservative `repeated_template_status` columns per slot row.
|
||||
- `USECODE/EUSECODE_extracted/boot_family_decompile.md` / `.tsv`, `callback_family_decompile.md` / `.tsv`, and `environmental_family_decompile.md` / `.tsv` now provide concrete generated per-class decompile artifacts for the `_BOOT`, `SURCAM*`, and environmental repeated-family lanes, each grounded in emitted output rather than prose-only examples.
|
||||
- `USECODE/EUSECODE_extracted/repeated_family_regressions.tsv` now records and enforces the current repeated-family slot sets plus the verified raw-row and derived body-window fields for `JELYHACK/JELYH2`, `_BOOT`, `SURCAMNS/SURCAMEW`, and `FLAMEBOX/NOSTRIL/STEAMBOX` so extractor changes fail fast if those verified baselines drift.
|
||||
|
||||
What this confirms on the current repeated families:
|
||||
|
||||
- `JELYHACK` and `JELYH2` slot `0x01` are exact row twins but not exact body twins. Both bodies are `42` bytes long, both start at `0x00d4`, both keep `raw_event_entry_word = 0x002A`, and both share a `10`-byte prefix plus a `17`-byte suffix. The first differences are at body offsets `10,11,12,24`, which is consistent with one reused mini-template carrying class-local literals rather than one identical compiled body.
|
||||
- `_BOOT` slot `0x10` is the cleanest repeated-body example. All five classes have a `59`-byte body, all share the same row word `0x003B`, all share the same first `5` bytes and the same last `17` bytes, and none are byte-identical across the family. This is strong evidence for one shared short-template tail with class-local identifiers or immediates in the middle.
|
||||
- `_BOOT` slots `0x0A` and `0x0F` show the same pattern at larger sizes. Slot `0x0A` bodies range from `551` to `843` bytes and share only a `3`-byte prefix but a `39`-byte suffix; slot `0x0F` bodies range from `564` to `604` bytes and share a `3`-byte prefix plus a `38`-byte suffix. These are repeated family bodies, but not clones.
|
||||
- `SURCAMNS` and `SURCAMEW` high slots `0x20` and `0x22` also behave like near-templates, not clones. Slot `0x20` is `698` bytes in both classes with an `11`-byte common prefix and an `84`-byte common suffix. Slot `0x22` is `419` bytes in both classes with an `11`-byte common prefix and a `53`-byte common suffix.
|
||||
- `SURCAM` slot `0x21` is the strongest within-family divergence in this batch. `SURCAMNS` uses row word `0x0709` and a body length of `1801`, while `SURCAMEW` uses row word `0x0655` and a body length of `1621`. They still share a `20`-byte suffix, so this is best read as one callback-family slot with materially different instance bodies rather than a parsing mistake.
|
||||
|
||||
The practical IR consequence is important: repeated-family status should be recorded separately from byte-identity status. A human-readable decompile should be able to say “same family slot template” without falsely implying “same body bytes.”
|
||||
|
||||
## What A Decompiled Script Looks Like Today
|
||||
|
||||
The most honest present-day decompilation is not a polished source language. It is a reversible descriptor-plus-event-table rendering with optional VM-op vocabulary attached where the `000d` lane is already verified.
|
||||
|
||||
### Level 0: Raw event row plus derived body window
|
||||
|
||||
This is the minimal human-usable row form. It preserves the original six-byte event entry, explains how the body window is derived, and records whether the slot looks like an exact twin, a near-template, or a unique body.
|
||||
|
||||
```yaml
|
||||
class_name: REE_BOOT
|
||||
slot: 0x10
|
||||
event_name_hint_scummvm: leaveFastArea
|
||||
raw_event_entry_word: 0x003b
|
||||
raw_code_offset: 0x000005a8
|
||||
code_base_minus_one: 0x00d3
|
||||
derived_body_start: 0x067b
|
||||
derived_body_end: 0x06b6
|
||||
derived_body_length: 59
|
||||
repeated_template_status: boot-event-core/shared-slot-0x10
|
||||
body_identity_status: non-identical; shared 5-byte prefix and 17-byte suffix across all five _BOOT bodies
|
||||
body_sha1: 577c61e9c4c6...
|
||||
```
|
||||
|
||||
Field meaning, using only what is currently verified:
|
||||
|
||||
- `class_name`: authoritative class label from object `1` in the owner-loaded class table
|
||||
- `slot`: authoritative numeric slot id from the event table; this is safer than any guessed semantic name
|
||||
- `event_name_hint_scummvm`: external label for slots `0x00..0x1f`; useful for orientation, not yet verified as the local class-specific meaning
|
||||
- `raw_event_entry_word`: the unresolved leading `u16` from the 6-byte event record; authoritative bytes, unresolved semantics
|
||||
- `raw_code_offset`: the authoritative row `u32`; currently best read as a 1-based offset relative to `code_base_minus_one`
|
||||
- `code_base_minus_one`: derived from bytes `8..11` in the class header using the current conservative rule
|
||||
- `derived_body_start` and `derived_body_end`: computed chunk-local byte window for the slot body; useful for diffing and future recompilation, and now emitted directly in the extractor outputs
|
||||
- `repeated_template_status`: whether the row participates in a repeated family pattern such as `JELY` anchor twin, `_BOOT` event core, or `SURCAM` callback template
|
||||
- `body_identity_status`: whether the extracted body bytes are exact twins, near-templates, or materially different within that family
|
||||
- `body_sha1`: stable digest for exact identity checks without pretending the digest itself has semantic meaning
|
||||
|
||||
### Level 1: Lossless event-table IR
|
||||
|
||||
This is the form that is closest to a future round-trip compiler.
|
||||
|
||||
```yaml
|
||||
class:
|
||||
entry_index: 0x0115
|
||||
class_id: 0x04d3
|
||||
class_name: JELYHACK
|
||||
class_object_index: 0x04d5
|
||||
raw_code_base_u32: 0x00d4
|
||||
code_base_minus_one: 0x00d3
|
||||
conservative_event_count: 32
|
||||
descriptor_fields:
|
||||
- referent
|
||||
events:
|
||||
- slot: 0x01
|
||||
event_name_hint_scummvm: use
|
||||
raw_event_entry_word: 0x002a
|
||||
raw_code_offset: 0x00000001
|
||||
derived_body_start: 0x00d4
|
||||
derived_body_end: 0x00fe
|
||||
derived_body_length: 42
|
||||
repeated_template_status: referent-anchor-twin/shared-slot-0x01
|
||||
body_identity_status: near-template-with-JELYH2
|
||||
confidence: authoritative-bytes, hinted-label
|
||||
```
|
||||
|
||||
That is already a real decompilation output. It keeps the exact slot id, the exact six-byte row contents, and the exact class-header facts, while refusing to pretend that `use` is already a proven semantic name for this class.
|
||||
|
||||
Here is the same style for one active event-bearing attachment class in the same island:
|
||||
|
||||
```yaml
|
||||
class:
|
||||
entry_index: 0x011b
|
||||
class_id: 0x04db
|
||||
class_name: REE_BOOT
|
||||
class_object_index: 0x04dd
|
||||
raw_code_base_u32: 0x00d4
|
||||
code_base_minus_one: 0x00d3
|
||||
conservative_event_count: 32
|
||||
descriptor_fields:
|
||||
- referent
|
||||
- event
|
||||
- counter
|
||||
- item
|
||||
events:
|
||||
- slot: 0x0a
|
||||
event_name_hint_scummvm: equip
|
||||
raw_event_entry_word: 0x034b
|
||||
raw_code_offset: 0x00000001
|
||||
derived_body_start: 0x00d4
|
||||
derived_body_end: 0x041f
|
||||
derived_body_length: 843
|
||||
repeated_template_status: boot-event-core/shared-slot-0x0a
|
||||
body_identity_status: same-family-body-not-identical
|
||||
confidence: authoritative-bytes, hinted-label
|
||||
- slot: 0x0f
|
||||
event_name_hint_scummvm: enterFastArea
|
||||
raw_event_entry_word: 0x025c
|
||||
raw_code_offset: 0x0000034c
|
||||
derived_body_start: 0x041f
|
||||
derived_body_end: 0x067b
|
||||
derived_body_length: 604
|
||||
repeated_template_status: boot-event-core/shared-slot-0x0f
|
||||
body_identity_status: same-family-body-not-identical
|
||||
confidence: authoritative-bytes, hinted-label
|
||||
- slot: 0x10
|
||||
event_name_hint_scummvm: leaveFastArea
|
||||
raw_event_entry_word: 0x003b
|
||||
raw_code_offset: 0x000005a8
|
||||
derived_body_start: 0x067b
|
||||
derived_body_end: 0x06b6
|
||||
derived_body_length: 59
|
||||
repeated_template_status: boot-event-core/shared-slot-0x10
|
||||
body_identity_status: same-family-body-not-identical
|
||||
confidence: authoritative-bytes, hinted-label
|
||||
```
|
||||
|
||||
And here is one callback-style multi-slot class, which shows why the high slots should stay numeric for now:
|
||||
|
||||
```yaml
|
||||
class:
|
||||
entry_index: 0x011c
|
||||
class_id: 0x04de
|
||||
class_name: SURCAMEW
|
||||
class_object_index: 0x04e0
|
||||
raw_code_base_u32: 0x00e6
|
||||
code_base_minus_one: 0x00e5
|
||||
conservative_event_count: 35
|
||||
descriptor_fields:
|
||||
- referent
|
||||
- textFile
|
||||
- monit
|
||||
- valueBox
|
||||
- passcode
|
||||
- link
|
||||
- code
|
||||
- screen
|
||||
- cameraEgg
|
||||
- trueRef
|
||||
- therma
|
||||
- eventTrigger
|
||||
- foundGun
|
||||
events:
|
||||
- slot: 0x01
|
||||
event_name_hint_scummvm: use
|
||||
raw_event_entry_word: 0x00f7
|
||||
raw_code_offset: 0x000000d2
|
||||
- slot: 0x0a
|
||||
event_name_hint_scummvm: equip
|
||||
raw_event_entry_word: 0x00d1
|
||||
raw_code_offset: 0x00000001
|
||||
- slot: 0x20
|
||||
event_name_hint_scummvm: null
|
||||
raw_event_entry_word: 0x02ba
|
||||
raw_code_offset: 0x000001c9
|
||||
derived_body_start: 0x02ae
|
||||
derived_body_end: 0x0568
|
||||
derived_body_length: 698
|
||||
repeated_template_status: callback-eventtrigger/shared-slot-0x20
|
||||
body_identity_status: same-family-body-not-identical
|
||||
- slot: 0x21
|
||||
event_name_hint_scummvm: null
|
||||
raw_event_entry_word: 0x0655
|
||||
raw_code_offset: 0x00000483
|
||||
derived_body_start: 0x0568
|
||||
derived_body_end: 0x0bbd
|
||||
derived_body_length: 1621
|
||||
repeated_template_status: callback-eventtrigger/shared-slot-0x21
|
||||
body_identity_status: same-family-body-not-identical
|
||||
- slot: 0x22
|
||||
event_name_hint_scummvm: null
|
||||
raw_event_entry_word: 0x01a3
|
||||
raw_code_offset: 0x00000ad8
|
||||
derived_body_start: 0x0bbd
|
||||
derived_body_end: 0x0d60
|
||||
derived_body_length: 419
|
||||
repeated_template_status: callback-eventtrigger/shared-slot-0x22
|
||||
body_identity_status: same-family-body-not-identical
|
||||
```
|
||||
|
||||
The extra derived fields are worth keeping because they answer the immediate human question that the bare event table does not: not only “which slots exist,” but also “how much body belongs to each slot” and “whether this body is a true clone or only a same-family variant.”
|
||||
|
||||
### Level 2: Friendly but still reversible hinted form
|
||||
|
||||
This is the highest-level script shape that is justified right now.
|
||||
|
||||
```text
|
||||
anchor JELYHACK(referent)
|
||||
|
||||
# authoritative event rows for the anchor itself
|
||||
slot 0x01 hint=use? raw_word=0x002A code_off=0x00000001 body=0x00D4..0x00FE family=JELY-anchor identity=near-template-with-JELYH2
|
||||
|
||||
# nearby attachment classes from the same local island
|
||||
attach REE_BOOT(referent,event,counter,item)
|
||||
slot 0x0A hint=equip? raw_word=0x034B code_off=0x00000001 body=0x00D4..0x041F family=_BOOT-core identity=shared-template-not-clone
|
||||
slot 0x0F hint=enterFastArea? raw_word=0x025C code_off=0x0000034C body=0x041F..0x067B family=_BOOT-core identity=shared-template-not-clone
|
||||
slot 0x10 hint=leaveFastArea? raw_word=0x003B code_off=0x000005A8 body=0x067B..0x06B6 family=_BOOT-core identity=shared-template-not-clone
|
||||
|
||||
callback SURCAMEW(referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun)
|
||||
slot 0x01 hint=use? raw_word=0x00F7 code_off=0x000000D2 body=0x01B7..0x02AE
|
||||
slot 0x0A hint=equip? raw_word=0x00D1 code_off=0x00000001 body=0x00E6..0x02AE
|
||||
slot 0x20 raw_word=0x02BA code_off=0x000001C9 body=0x02AE..0x0568 family=SURCAM-callback identity=shared-template-not-clone
|
||||
slot 0x21 raw_word=0x0655 code_off=0x00000483 body=0x0568..0x0BBD family=SURCAM-callback identity=shared-template-with-stronger-divergence
|
||||
slot 0x22 raw_word=0x01A3 code_off=0x00000AD8 body=0x0BBD..0x0D60 family=SURCAM-callback identity=shared-template-not-clone
|
||||
|
||||
attach SFXTRIG(referent,event)
|
||||
slot 0x0A hint=equip? raw_word=0x00B8 code_off=0x00000001
|
||||
```
|
||||
|
||||
This is decompiled enough to read, diff, and later recompile because it preserves:
|
||||
|
||||
- the original class identity
|
||||
- the exact non-zero event rows
|
||||
- the derived chunk-local body window for each row
|
||||
- which names are authoritative fields versus external hints
|
||||
- which nearby descriptors appear to be anchors, active event attachments, or callback attachments
|
||||
- whether a repeated family slot is an exact twin or only a structurally similar body
|
||||
|
||||
### Level 2.5: Human annotation layer
|
||||
|
||||
The last layer is prose, not syntax. It should explain the honest current reading of each field so a modder can see what is safe to edit and what still needs caution.
|
||||
|
||||
- Class name is authoritative at the container level: it comes from the owner-loaded class-name table and is not a guess.
|
||||
- Slot id is authoritative at the event-table level: this is the safest event identifier currently available.
|
||||
- Event-name hint is external: use it as orientation only when the slot is inside `0x00..0x1f` and the local behavior has not yet been reverified in binary.
|
||||
- Raw event word is authoritative but semantically unresolved: it must survive round-trip intact.
|
||||
- Raw code offset is authoritative and operational: combined with `code_base_minus_one`, it tells us where the slot body starts in the chunk.
|
||||
- Body-window length is derived but useful: it tells a human whether a slot is a tiny stub-like record or a large body that deserves its own diff or annotation block.
|
||||
- Repeated-template status is about family structure, not byte identity: a `_BOOT` slot can be “the same template role” without being byte-equal across classes.
|
||||
- Body-identity status answers the concrete modding question “am I looking at a clone, a parameterized variant, or a different body that only occupies the same family slot?”
|
||||
|
||||
### Level 3: Where the current VM IR can be attached
|
||||
|
||||
For classes in the active-event ecosystems (`EVENT`, `_BOOT`, `NPCTRIG`, `SFXTRIG`, and the environmental family), the current `000d` work is strong enough to attach the known operator vocabulary without pretending one exact class-to-opcode decode already exists.
|
||||
|
||||
```text
|
||||
vm_effect_possible:
|
||||
APPEND_UNIQUE_INLINE
|
||||
APPEND_UNIQUE_INDIRECT
|
||||
REMOVE_MATCHING_INDIRECT
|
||||
REMOVE_MATCHING_INLINE
|
||||
MATERIALIZE_OR_FORWARD_VALUE
|
||||
PREPEND_INLINE_PAYLOAD
|
||||
BUILD_ENTITY_LINK_MATRIX
|
||||
EMIT_OR_PUSHBACK_RESULT
|
||||
FINALIZE_MIXED_VALUE_TO_OUTPTR
|
||||
```
|
||||
|
||||
That operator block is authoritative as a recovered VM vocabulary, but only ecosystem-level when attached to one specific descriptor family.
|
||||
|
||||
## Conservative Parser Rule To Adopt Now
|
||||
|
||||
For the current owner-loaded EUSECODE and round-trip IR work, the safest reversible rule is:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue