- Introduced a new command 'annotate-usecode' to import USECODE IR JSON annotation hints as Ghidra comments on compiled anchors. - Added argument parsing for multiple IR JSON files, comment type selection, and a dry-run option. - Implemented logic to read annotation records from the provided IR files and set comments on the corresponding addresses in Ghidra. - Enhanced JSON schema to include response structure for the new command.
80 KiB
Raw 0008 & 000c: Dispatch Helpers & State Machine
Content extracted from crusader_decompilation_notes.md. Covers the 0008 gameplay dispatch helper cluster and all 000c state machine helpers.
Raw 0008 Gameplay Dispatch Helper Batch
Small conservative rename batch from direct field-write behavior in the 0008:ba00-0008:be05 cluster.
Newly renamed functions
| Address | Name | Evidence |
|---|---|---|
0008:ba00 |
entity_dispatch_entry_init |
Constructor-style init: optional alloc (0x32 bytes), vtable/list-link setup (0x3b06, 0x2d10, 0x3afe), zeroes state fields, seeds group from global active layer 0x39c9 via entity_set_group_id |
0008:bbb6 |
entity_set_source_type |
Writes entry word field +0x04 from incoming parameter, then dispatches through FAR thunk path |
0008:bc27 |
entity_set_event_type_checked |
Writes entry word field +0x06; when source field +0x04 is non-zero, validates old/new event transition, including special checks for 0xF0-0xF7 and upper bound <= 0x0FFF |
0008:bca8 |
entity_set_group_id |
Validates group id range 1..31, writes low 5-bit group in byte +0x08, decrements old per-group counter and increments new one via counter table pointed to by 0x39c5 |
0008:bd53 |
entity_dispatch_entry_unlink |
Clears bit 0x1000 in flags2 at +0x18 and zeroes the four link/state words at +0x0a..+0x10; used as the common unlink/reset tail in the local dispatch-entry pruning path |
0008:be05 |
entity_increment_group_id |
Computes ((entry+0x08)&0x1F)+1, validates against active-layer assumptions (0x39c9), then applies through entity_set_group_id |
Verified call/xref notes
entity_set_group_idis called fromentity_dispatch_entry_init(0008:bae4) andentity_increment_group_id(0008:be57).entity_set_source_typeis used fromFUN_0008_c92f(0008:c94d,0008:c96d) andFUN_0008_ca18(0008:ca36,0008:ca56).0008:bd79remains positional, but current evidence shows it compares an entry extent/position tuple against the player world position (g_player_entity_farptr + 0x40/+0x42), optionally fires the vtable callback at+0x28when flag0x100is armed, then callsentity_dispatch_entry_unlink.
Gameplay relevance
This cluster manages core dispatch-entry metadata (source_type, event_type, group/layer byte and counters) that feeds the seg021 scheduler/event system. The field offsets match the current seg021 entity/dispatch layout notes (+0x04, +0x06, +0x08).
Raw 0008 Pair-Sync Helper Batch
Conservative directional rename batch from the 0008:c7f1-0008:cad7 cluster. These functions are clearly paired and structurally symmetric, but final gameplay semantics are still partial due to FAR-thunk heavy internals.
Newly renamed functions
| Address | Name | Evidence |
|---|---|---|
0008:c7f1 |
entity_pair_update_link_slot_a |
Guards on entry flags (+0x16 must not include 0x4000), then dispatches through FAR thunk using entry local struct at +0x28 and partner-side key/id input |
0008:c890 |
entity_pair_update_link_slot_b |
Twin of entity_pair_update_link_slot_a with identical call shape and guard behavior; used in opposite order by pair-sync wrappers |
0008:c92f |
entity_pair_sync_a |
If either side has unset source_type (+0x04), copies from partner via entity_set_source_type; then calls link-slot helpers in A-order and ends in FAR thunk using first side +0x1e data |
0008:ca18 |
entity_pair_sync_b |
Mirror of entity_pair_sync_a with reversed side/order for helper calls and final thunk argument ordering |
0008:c9ee |
entity_pair_mark_and_sync_a |
Sets bit 0x10 in entry flags at +0x16, then calls entity_pair_sync_a |
0008:cad7 |
entity_pair_mark_and_sync_b |
Sets bit 0x10 in entry flags at +0x16, then calls entity_pair_sync_b |
Raw 0008 Flag-0x20 Target-State Helpers
Two complementary helpers near the pair-sync cluster.
| Address | Name | Evidence |
|---|---|---|
0008:cb2c |
entity_flag20_clear_and_update_target |
Clears bit 0x20 at entry flags +0x16; if non-null target args are provided, writes far-pointer target fields +0x12/+0x14; then calls shared refresh helper 0008:c01d |
0008:cb5c |
entity_flag20_set_and_init_target |
Sets bit 0x20 at entry flags +0x16; initializes target far-pointer fields +0x12/+0x14 only when currently zero; then calls shared refresh helper 0008:c01d |
Both helpers share the same post-update refresh path (0008:c01d), suggesting they are two state transitions in one target/link-management subsystem.
Raw 0008 Dispatch Refresh Pipeline
Follow-up rename batch for the shared refresh node used by the flag-0x20 helpers.
| Address | Name | Evidence |
|---|---|---|
0008:c01d |
entity_refresh_dispatch_state |
Early-exit when flags at +0x16 indicate dead (0x8) or already refreshed (0x4000); otherwise runs pre-clear, sets 0x4000, calls update vfunc path, then runs flag-conditioned handlers |
0008:bfb2 |
entity_clear_status_bits_from_flags |
Clears specific bits in status word at +0x32 based on state flags (+0x16:0x400, +0x18:0x40/0x80) |
0008:bf8e |
entity_call_update_vfunc14 |
Calls helper 0008:be6b, then dispatches entity vtable call at offset +0x14 |
0008:beee |
entity_run_flagged_handlers |
Executes handler calls gated by flags (+0x16:0x400/0x4, +0x18:0x40/0x80) and then dispatches via FAR thunk using entry slot/index (+0x2) |
State pipeline after target/link changes: flag-gated status clear → mark refreshed (0x4000) → vtable update callback → flag-conditioned subsystem handlers.
Raw Import Note: 0000:ffff Thunk Target
FUN_0000_ffff renamed to unresolved_far_thunk_dispatch. Current raw-import evidence indicates this is not valid local executable logic in this program view:
- Decompiler emits overlapping-instruction warnings and bad-control-flow warnings.
- Disassembly from
0000:ffffinto0001:xxxxis nonsensical/misaligned (mixed data/code artifacts). - The body is heavily shared as a call sink from many segments, consistent with unresolved inter-segment thunking in this import mode.
Treat calls to unresolved_far_thunk_dispatch as unresolved external/indirect dispatch edges. Semantic recovery should continue from call-site argument setup and local field effects.
Raw 0008 Flag-0x100 and Constructor-Variant Batch
| Address | Name | Evidence |
|---|---|---|
0008:d1a4 |
entity_set_flag100_in_flags2 |
Gate-checked setter: ORs bit 0x100 into entry word at +0x18 |
0008:d1dc |
entity_clear_flag100_in_flags2 |
Gate-checked clearer: ANDs entry word at +0x18 with 0xFEFF (clears bit 0x100) |
0008:cefb |
entity_dispatch_entry_ctor_vtbl_3ad2 |
Constructor variant: allocates if null, reinitializes via entity_dispatch_entry_init, sets vtable 0x3ad2, sets flag 0x100 at +0x16, and zeroes the extension words at +0x32/+0x34 |
0008:d214 |
entity_dispatch_entry_ctor_vtbl_3aa6 |
Constructor variant: allocates 0x40 bytes if null, reinitializes via 0008:cefb, sets vtable to 0x3aa6, sets flag 0x200 at +0x16, zeroes fields +0x38..+0x3e |
Raw 0008 Periodic/Counter Helpers
Follow-up renames from the 0008:d313-0008:d47d cluster tied to the 0x3aa6 constructor branch.
| Address | Name | Evidence |
|---|---|---|
0008:d313 |
entity_periodic_accumulate_and_dispatch |
Adds global delta (0x39d0/0x39d2) into entry accumulator (+0x3c/+0x3e), wraps against period (+0x38/+0x3a), and on wrap invokes entry vtable callback at +0x28 with reentrancy guard bit 0x400 in +0x18 |
0008:d3e6 |
entity_set_flag2000_and_update_active_counters |
Atomic (CLI/PUSHF) set of bit 0x2000 in +0x16; if bit 0x400 is set, decrements global counter 0x39f6 and increments 0x39f4 |
0008:d433 |
entity_clear_flag2000_and_update_active_counters |
Atomic clear of bit 0x2000 in +0x16; if bit 0x400 is set, decrements 0x39f4 and increments 0x39f6 |
The 0x39f4/0x39f6 counter swap implies global bookkeeping for a scheduler subset associated with these entries.
Raw 0008 Word-List Management Batch
Verified helper cluster for entry-owned word-list storage (sentinel-terminated with 0x0408).
| Address | Name | Evidence |
|---|---|---|
0008:da00 |
entity_word_list_set_0408_terminated |
Rebuilds/replaces entry list from stack-provided words terminated by 0x0408; frees prior list pointer at +0x06/+0x08; allocates and populates new list |
0008:dba3 |
entity_word_list_free_existing |
Validates list pointer exists, then frees old list buffer referenced by +0x06/+0x08 |
0008:dbec |
entity_word_list_destroy |
Resets vtable to 0x2d10, frees list if present via entity_word_list_free_existing, and optionally frees object when destroy flag bit 1 is set |
0008:dc38 |
entity_word_list_ensure_contains |
Scans existing list for a given word; if missing, appends through entity_word_list_append_unique |
0008:dcab |
entity_word_list_append_unique |
Allocates larger list, copies existing words, appends new word plus 0x0408 terminator, frees old list, then rebuilds via entity_word_list_set_0408_terminated |
Entry fields used by this subsystem: count at +0x02, list far pointer at +0x06/+0x08. The explicit 0x0408 terminator appears in both scanner/build logic and append path.
Raw 0008 Word-List Access/Mutation Batch
Follow-up renames extending the same list subsystem.
| Address | Name | Evidence |
|---|---|---|
0008:deea |
entity_word_list_get_at |
Bounds-checks index against count (+0x02) and returns word from list pointer (+0x06/+0x08, stride 2) |
0008:df1b |
entity_word_list_set_at |
Bounds-checks index then writes value into list element (+0x06/+0x08, stride 2) |
0008:dfa1 |
entity_word_list_find_unflagged_by_id10 |
Scans list and returns first value satisfying (value & 0x400)==0 and (value & 0x3ff)==requested_id; writes 0 when not found |
0008:ddaf |
entity_word_list_remove_value |
Removes matching value(s) by counting survivors, rebuilding compact storage for non-matching entries, freeing old list storage, and updating list state |
List entries pack a 10-bit id plus flag bits (0x400 observed).
Raw 0008 Gate-Callback Wrapper Batch
Conservative renames for callback wrappers sharing the same global gate condition.
| Address | Name | Evidence |
|---|---|---|
0008:d00e |
entity_gate_callback_wrapper_a |
Gate check on globals 0x39a8/0x39f9/0x3991; on pass dispatches callback through unresolved thunk using entry +0x2 and [0x3b32:0x3b34] + 0x32 |
0008:d05f |
entity_gate_callback_wrapper_b |
Same gate pattern; callback wrapper variant via unresolved thunk |
0008:d0b0 |
entity_gate_callback_wrapper_c |
Same gate pattern; passthrough-style callback wrapper |
0008:d0ed |
entity_gate_callback_wrapper_d |
Same gate pattern; passthrough-style callback wrapper |
0008:d12a |
entity_gate_callback_wrapper_e |
Same gate pattern; passthrough-style callback wrapper |
0008:d167 |
entity_gate_callback_wrapper_f |
Same gate pattern; passthrough-style callback wrapper |
0008:d3d2 |
entity_slot_callback_wrapper |
Thin wrapper: pushes entry slot/index (+0x2) and dispatches through unresolved thunk |
Additional Unresolved Thunk Stubs
Follow-up thunk census after inspecting 0000:ffff behavior. All of the following are single-instruction wrappers (CALLF 0000:ffff):
| Address | New Name | Observed Caller(s) |
|---|---|---|
0004:2592 |
thunk_callf_0000_ffff_0004_2592 |
0004:262d (FUN_0004_2620) |
000b:f924 |
thunk_callf_0000_ffff_000b_f924 |
000b:0144 (FUN_000b_010b) |
000c:827d |
thunk_callf_0000_ffff_000c_827d |
000c:8985, 000c:8f96 (FUN_000c_88b4) |
000c:82f9 |
thunk_callf_0000_ffff_000c_82f9 |
000c:8a10, 000c:8f79, 000c:9052 |
000c:8356 |
thunk_callf_0000_ffff_000c_8356 |
000c:84a9 (FUN_000c_84a5) |
000c:e4f9 |
thunk_callf_0000_ffff_000c_e4f9 |
000c:e4f5 (FUN_000c_e4e0) |
unresolved_far_thunk_dispatch is represented by multiple local trampoline copies in different segment regions. Separating them by address improves call-graph navigation.
Raw 000c State-Dispatch Helper Cluster
After separating thunk stubs, a coherent local state/chain management cluster was lifted in 000c:ab32-000c:ac8f.
| Address | Name | Evidence |
|---|---|---|
000c:ab32 |
entity_state_tick_dispatch |
Core state tick helper using fields +0x38/+0x39/+0x3b/+0x3d/+0x5b; clears mode bit 0x100 when +0x38==0, may call cleanup helper 000c:ac55, calls 000c:7730(state,1), and conditionally advances chain |
000c:ab96 |
entity_state_reset_and_tick_dispatch |
Reset wrapper: zeroes +0x38 and +0x39 then calls entity_state_tick_dispatch |
000c:abb4 |
entity_state_advance_next_or_fallback_a |
Advance path A: when +0x49!=0, follows node-next pointers from +0x3b/+0x3d using offsets +2/+4; when exhausted, either clears active flag and re-dispatches, or falls back to backup pointer +0x41/+0x43 |
000c:ac8f |
entity_state_advance_next_or_fallback_b |
Advance path B: same structure as A but follows alternate node offsets +6/+8 and fallback pointer +0x45/+0x47 |
Raw 000c State-Flag Guard / Input Handler Batch
Second sweep through 000c adjacent helpers — gated thunk wrappers and input/animation tick handlers.
| Address | Name | Evidence |
|---|---|---|
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 [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 |
transition_file_family_select_and_refresh |
Local startup/display selector: field49==-1 normalizes to 0; field49==2 dispatches vtable[0x3c]; field49==0/1/4 composes one of three sibling filenames from inherited base 0x6aa:0x6ac plus stem/suffix buffers 0x621c/0x6223, 0x621c/0x622d, or 0x621c/0x6237, loads the result into object +0x520, then runs the shared redraw/palette/input refresh path |
000c:b153 |
transition_file_family_advance_on_anim_tick |
Polls [param_2+0x14+0xa]; when clear increments field49 and re-enters transition_file_family_select_and_refresh, otherwise exits through vtable[0x3c] |
000c:b199 |
transition_file_family_input_key_handler |
Local selector key handler: ESC/x/X → vtable[0x3c]; Left/Right arrows 0x14b/0x148 → previous file-family state; n/N/0x14d/0x150 → next state; e/E arms field47; - after arming counts up to forced state 4; selector moves drain the event queue and clear 0x8a94/0x8a96/0x8a98 |
000c:b2c3 |
stub_noop_000c_b2c3 |
Empty stub; returns immediately |
000c:b2c8 |
entity_state_dispatch_if_field49_eq4 |
Fires thunk only when [ptr+0x49]==4 |
000c:b349 |
entity_state_dispatch_if_far_ptr_nonzero_a |
Fires thunk if far-pointer args non-zero |
000c:b383 |
entity_state_set_field3f_and_dispatch |
If non-NULL: writes &DAT_0000_2d18 to [ptr+0x3f], then dispatches |
000c:b3d8 |
entity_state_dispatch_if_far_ptr_nonzero_b |
Same null-guard pattern as b349, variant b |
Patterns confirmed:
field49= local transition file-family selector state in this startup/display family;0/1/4choose sibling filenames under shared base0x6aa:0x6acplus stem0x621c,2dispatchesvtable[0x3c], and-1normalizes back to0field47= keystroke arm/counter for the locale/Ethen-path into selector state4field3f= linked data pointer (event/record reference)[0x6054]= current entity handle;[0x6828]=g_active_dispatch_entry_farptr, the shared active-dispatch entry owner whose byte+0x40is 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
Raw 000c Palette Fade + Entity VM Cluster
VGA Palette Fade
| Address | Name | Evidence |
|---|---|---|
000c:cdde |
palette_fade_step_down |
Writes (R−offset, G−offset, B−offset) clamped to 0 to VGA I/O 0x3c8/0x3c9; decrements [0x630d] by step [0x6316]; clears active at [0x630a] when black |
000c:ce57 |
palette_fade_step_up |
Same loop, adds offset, clamps at 63 (0x3f full VGA). Clears [0x630a] when fully bright |
Globals used: [0x6312]=start index, [0x6314]=count, [0x630e]=palette src ptr, [0x630d]=brightness offset, [0x6316]=step, [0x630a]=active flag.
Entity Mini-VM / Record-Player Context
| Address | Name | Evidence |
|---|---|---|
000c:f6b8 |
record_table_get_by_index |
Bounds check param < [0x8c88]; return word at [0x8c84 + param*4]. Table at 0x8c84 |
000c:f6e8 |
entity_vm_stack_init_with_data |
Init stack ptrs at [ptr+0xcc..+0xd4] pointing to self; max depth 199; copies optional initial data |
000c:f772 |
entity_vm_state_copy |
Copies 200 bytes (100 words from [src+4] to [dst+4]), then copies 4 words at +0xcc..+0xd2 |
000c:f7c7 |
entity_vm_stack_push_frame |
Push call-frame: saves ret offset at [ptr+0xd4], decrements [ptr+0xcc] by param_size, zeroes new frame |
Current EUSECODE / event bridge notes
entity_vm_set_value_from_slot_plus_offset(000c:f95f) now provides a concrete bridge from the000cmini-VM cluster into the000devent/countdown lane:- it calls
FUN_000d_5572(*(word *)0x6611, *(word *)0x6613, param_3, param_4, 0, 0) - then stores the returned far pair into target object fields
+0xd6/+0xd8
- it calls
entity_vm_slot_load_value_plus_offset(000d:5572) is a thin wrapper overentity_vm_slot_load_value(000d:51fd), but the previously suspiciousPUSH 0x410path at000d:5290is now reclassified: it pushes0x410,DS, and0x6616into the seg091 fatal-report helper at000a:44fd, so this is an error/assert path rather than a live gameplay event dispatch.- This closes the earlier compiled-code immortality bridge from
000c:f95finto000d:51fd. The verified bridge that remains is the data/value handoff into the context+0xd6/+0xd8lane, not a direct event0x410producer. - Supporting renamed helpers in the same lane now include:
entity_vm_slot_find_or_select(000d:4e7c): scans 0x26-byte slot records, returns a matching slot id when present, and tracks one fallback slot for reuse/evictionentity_vm_slot_decrement_use_count(000d:558d): decrements one slot-use counter and traps on underflowentity_vm_slot_release_value(000d:5617): releases one slot value, restores the owner's0x1300/0x1302budget pair, writes the slot state back to-1, and notifies through000a:2b9dentity_vm_opcode_finish(000d:3350): shared VM opcode epilogue used by the000d:039f,000d:08a2,000d:0988,000d:177c, and000d:1acbhandlers; if the local result slot is non-zero it writes the current referent id to0x8c94, optionally pops oneslot_arrayframe through0x659c/0x659e, and returns the opcode result from local stateentity_vm_referent_chain_remove_matching_from(000d:6a9a): destructive chain-difference helper used by the0x1a/0x1bopcode path in000d:0988; it walks one source chain against a destination chain, removes matching entries in place, and frees removed registry nodes / indirect payloadsentity_vm_referent_chain_set_entry_data_at(000d:6cf6): finds one chain entry by index and overwrites its payload in place, including indirect/string cleanup when the chain uses indirect storage
- The surrounding runtime/context family is now materially clearer too:
entity_vm_runtime_create/entity_vm_runtime_init_slots/entity_vm_runtime_release_slots/entity_vm_runtime_destroy(000d:4c99,000d:4d36,000d:4d75,000d:4e01) are the global0x6611owner for this lane; they allocate the 0x2040-byte runtime body, clear the 0x80-entry slot table, manage the runtime budget/default fields at+0x1300..+0x1314, and retain one owner/resource object at+0x1315/+0x1317returned by000d:7000entity_vm_slot_index_from_entity(000d:45c5) computes one slot index from a gameplay entity by branching on seg021 class/type helpers and then adding one of the current runtime base offsets0x8c7c/0x8c7e/0x8c80entity_vm_context_try_create_masked_for_entity(000d:463a) uses that slot index to test one owner-side mask entry before it creates a context, which is the strongest current bridge from gameplay entities into this VM laneentity_vm_context_create_from_slot_index(000d:46ec) allocates one0x6714context object, seeds its+0xd6/+0xd8lane throughentity_vm_slot_load_value_plus_offset, initializes the local mini-VM state, and can prepend caller data into the backward-growing buffer at+0x102entity_vm_opcode_sequence_run(000d:ebe3) is now named conservatively in Ghidra: it seeds the stage chain from object+0xfe, runs000d:177c -> 000d:1acb -> 000d:0988 -> 000d:22bc -> optional 000d:1d4a -> 000d:2104, then finishes with tracked-handle cleanup plus the0008:ebe7gate on object+0xc0and byte+0x4bentity_vm_context_sync_global_value_and_dispatch(000d:48da) is the current context-side runner/sync point: it marks the context busy at+0x123, callsentity_vm_set_field_da_to_global, optionally writes the current value through+0x11b/+0x11d, and dispatches through the context vtable on successentity_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 whole000d:45xx..4exxisland anonymous
entity_vm_context_try_create_masked_for_entityis now better constrained at the return-value level too: after the runtime-disable check at0x6610and 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 withdos_file_handle_init(0009:1c00), then uses helper vtable slot+0x04as the size query that drives the child+0x10/+0x12allocation and helper vtable slot+0x0cas the table-population callback for the0x0d-stride owner table.- That file-backed helper is now tighter one step deeper as well. The seg070 loops rooted at raw windows
0009:67b6and0009:6916walk 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 throughfile_handle_alloc_init_and_open(0009:1c3a),dos_file_seek(0009:2034), anddos_file_close(0009:1e61). The paired+0x18entries are consumed as 16-bit ids passed into those path-format loops beside the far-pointer path table at+0x10; no object-1 orclassid + 2arithmetic appears there, so the safest current read is slot-local file ids rather than exposed original class/object indices. That is strong evidence that000d:7000seeds 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:67b6and0009:6916now read as paired file-family walkers rather than one isolated path-format callback. Both windows iterate the helper-owned count at+0x14, index the far-pointer path table at+0x10and paired 16-bit id table at+0x18, check the source path through0003:e669, build formatted paths with distinct local format strings (DS:3f2dvsDS:3f40), and then reach the same file open/read/close lane. Each loop also writes into its own independently allocated output far buffer before the shared trailer runs, so the best current reading is two parallel file families or record banks loaded by the same helper rather than two phases over one shared buffer. The remaining open question is the exact per-family record schema and higher-level resource role, not whether the helper is file-backed. - 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 at0x65a, builds a path through seg072 helper0009:3600using globals0x6d6:0x6d8plus0x65a, validates that path through000a:500a, then callsentity_vm_runtime_create(0,0,path). This is the first verified source-argument path forentity_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:3600is now classified more tightly as a rotating slash-aware path composer rather than a generic buffer advance helper. Its prologue cycles through five0x50-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 by000d:44df:0x65abehaves as the configured relative runtime-owner filename/path component, while0x6d6:0x6d8behaves as the mutable base/resource-root path buffer that gets joined with0x65abefore000a:500avalidation. - The two still-xref-dark wrappers
0005:2c35and0005:2c68are also narrower now. Their signed extra word does not participate in owner-mask selection insideentity_vm_context_try_create_masked_for_entity; it is forwarded intoentity_vm_context_create_from_slot_index, stored in context field+0x34, and passed on toentity_vm_slot_load_value_plus_offset. The best current reading is thereforeoffset-specialized masked context creation, not a separate direct selector lane. - Ghidra now records that signed-offset contract directly in the wrapper names too:
0005:2c35=entity_vm_context_try_create_mask_0400_slot0a_with_offsetand0005:2c68=entity_vm_context_try_create_mask_0800_slot0b_with_offset. That still stops short of real caller-role recovery, but it removes the last ambiguity about whether the extra stack word is semantically live. - The first opcode-level behavior split inside that runtime is now visible in the
000d:0988family:- one branch calls
entity_vm_referent_chain_append_unique_from, which looks like an attach/union operation on the current referent payload chain - the
0x1a/0x1bbranch instead callsentity_vm_referent_chain_remove_matching_from, which looks like the inverse operation and makes the opcode family materially closer to a graph-editing script VM than a flat event list - both paths return through
entity_vm_opcode_finish, so the referent-global write to0x8c94is now better understood as a shared interpreter epilogue rather than a unique quirk of one helper
- one branch calls
- One additional runtime layer is now named under that context family: the referent registry at
0x8c8c/0x8c8e/0x8c90/0x8c94.entity_vm_referent_registry_init/entity_vm_referent_registry_destroy(000d:6000,000d:60bf) allocate and free the registry buffer, seed the free-list/root metadata, and clear the current referent id at0x8c94entity_vm_referent_registry_alloc(000d:613e) allocates one registry node from the free list and stores the current referent id from0x8c94into node field+0x04entity_vm_referent_registry_release_by_id/entity_vm_referent_registry_free_node(000d:6251,000d:62ac) release all live nodes for one referent id and coalesce adjacent free nodes- this makes
entity_vm_set_field_da_to_globalmore important than it first looked: it writes0x8c94from the current context+0xdalane and then immediately enters the still-misaligned000c:3350body, so the referent selected by the context is now visibly feeding runtime registry state - the registry nodes are not flat scalars only; the surrounding container helpers are now named too:
entity_vm_referent_chain_copy/entity_vm_referent_chain_append_unique_from(000d:6694,000d:68c3) build deep-copied or deduplicated chains of referent-linked payloadsentity_vm_referent_chain_destroy,entity_vm_referent_chain_next, andentity_vm_referent_chain_append_node(000d:6602,000d:6651,000d:687b) provide the list management shellentity_vm_referent_chain_contains_entry,entity_vm_referent_chain_get_entry_data_at, andentity_vm_referent_chain_get_indirect_data(000d:6c31,000d:67f2,000d:6860) show that some chains carry fixed-size inline payloads while others carry indirect string-like payload nodes
- This matters for script readability: the current runtime model is no longer only "one referent id hits one event." It now supports a more useful intermediate representation where one referent anchor can own one or more payload chains, and neighboring event-bearing descriptors can attach behavior to that anchor without duplicating the anchor's own record.
- Nearby descriptor work on
EUSECODE.FLXis consistent with that model: event-bearing classes (EVENT,NPCTRIG,SFXTRIG, several*_BOOTrecords) use a stable69:0A00 -> eventtag, whileJELYHACK/JELYH2remain referent-only descriptors in a neighborhood that includesTRIGPAD,SPECIAL,REE_BOOT,SURCAMEW, andSFXTRIG. - The strongest current callsites into this context-construction path are the large
000d:208band000d:21edbodies, which both feed per-object stream/data state from+0xcc/+0xceintoentity_vm_context_create_from_slot_indexbefore continuing bytecode-style reads from the newly seeded+0xd6/+0xd8lane. That makes the000dinterpreter/object lane a better current immortality target than the older000etext-parser hypothesis. - The immediate producer chain for that
+0xcc/+0xcestream state is now one layer tighter:entity_vm_context_create_from_slot_index(000d:46ec) allocates the0x6714context, then callsentity_vm_context_setup(000c:f844) on the embedded mini-VM object at context+0x36.entity_vm_context_setupdelegates toentity_vm_stack_init_with_data(000c:f6e8), which seeds[mini_vm+0xcc..+0xd2]to point into the object's own payload area and optionally prepends caller-owned inline bytes by moving the stack pointer backward.entity_vm_state_copy(000c:f772) copies that same+0xcc..+0xd2stream/base quartet verbatim when one mini-VM object is cloned.- Upstream of the setup helper,
000d:46ecderives the source payload from the runtime owner table behind0x6611 -> +0x1315/+0x1317: with slot indexSI, it walks owner table*(owner+0x10/+0x12) + 0x0d*SI + 4, passes that far pointer into000c:f844, and mirrors the resulting per-slot source into0x39ca[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
JELYHACKorJELYH2. Combined with the extractor evidence thatJELYHACK/JELYH2remain referent-only whileREE_BOOT/SFXTRIGkeep activeeventtags andSURCAMEWkeepseventTrigger, the better fit is stillreferent anchor -> slot-backed payload chain -> neighboring event-bearing attachment. - The
0x39camirror question is now split more cleanly. Fresh windows at0008:709c/70cb,0008:7309/7338, and0008:85f9/8617still show only global base-pointer save/restore and allocation/zeroing of the0x39ca:0x39cctable itself, but two additional per-slot row writers are now verified in000d:FUN_000d_7299writes static sourceDS:67f2to0x39ca[obj+2]after creating a0x44-byte object, andactive_dispatch_entry_create_default(000d:761c) writes static sourceDS:6872to0x39ca[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 + 4before mirroring it into0x39ca[slot], while000d:7299and000d:761cnever 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 seed0x39ca[row]from fixed static sourcesDS:67f2andDS:6872. The safest current interpretation is thereforeowner-backed VM source mirrorversusdispatch-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:0953in the animation/audio lane pushes literal0x410into importedASYLUM.27immediately after setting the local audio-completion byte at+0xef1. BecauseASYLUM.DLLis theASS_*audio/media library, this does not weaken the attribution of gameplay event0x410to the000dVM/USECODE lane. - Current best JELYHACK reading after this pass:
JELYHACKitself 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 asREE_BOOT,SURCAMEW, andSFXTRIGsupply the event-bearing logic attached to the same local object island.
000d:21ed/22bc id-correlation table (runtime lane vs descriptor families)
| Runtime element | Code anchors | Observed width/shape | Correlation status |
|---|---|---|---|
| Metadata byte A | 000d:22d2 after context from 000d:46ec |
1-byte signed (CBW), used as first loop dimension/count input |
Not a descriptor id. Behaves as compact shape/count metadata for matrix construction. |
| Metadata byte B | 000d:22ee |
1-byte signed (CBW), paired with byte A and summed to derive loop bounds |
Not a descriptor id. Same shape/count role as byte A. |
| Streamed words feeding matrix | 000d:2324, 000d:2372, 000d:237b -> 0008:7d27 |
16-bit words consumed from caller stream and passed to entity_link |
Best fit: runtime entity/link ids, not descriptor-class selectors. |
| Matrix output writeback filter | 000d:23da..2421 |
tests 0x0400; only non-0x0400 words are pushed back |
Matches entity_word_list style link-flag semantics, not event opcode tagging. |
| Source stream provenance | 000d:4732..4751, 000d:47a3..47d4 |
source pointer = owner table (+0x10/+0x12) + 0x0d*slot + 4; mirrored to 0x39ca[slot] |
Slot-indexed runtime source table, generic across gameplay entity lanes. |
Conservative interpretation after this pass:
- The
000d:21ed -> 000d:22bclane is strongly supported as a slot-backed payload to entity-link closure path, where two signed byte-sized metadata fields shape an exactA x Bmatrix walk: byte A is the lead-word row count, byte B is the shared target-list width, and the word entries passed toentity_linkare runtime link/entity ids rather than descriptor selectors. - Descriptor-family alignment is therefore stronger with generic active event ecosystems (
EVENT/NPCTRIG/*_BOOT/SFXTRIG) than withSURCAM*callback holders, because no directeventTrigger-specific discriminator is read in this lane. - Direct descriptor-id attribution is still rejected for now: no code evidence ties the consumed bytes/words here to explicit EUSECODE class indices or to a hard
JELYHACK/SURCAM*switch. - The new extractor-side structure pass tightens the descriptor-side fit inside that generic active-event ecosystem.
USECODE/EUSECODE_extracted/immortality_body_structure.mdshowsEVENTslot0x0aas a broad hub clause stream with90internal0x53 0x5c <u16> EVENTsubheaders and the widest field trailer, whileNPCTRIGslot0x0astays compact at5subheaders and a narrowreferent/event/item/item2tail. That does not prove a direct class-id bridge into000d:21ed -> 000d:22bc, but it does makeNPCTRIG slot 0x0athe strongest remaining compact descriptor-side candidate for the offset-specialized slot-0x0aruntime wrapperentity_vm_context_try_create_mask_0400_slot0a_with_offset(0005:2c35) instead of the older undifferentiatedEVENT or NPCTRIGfrontier. - The next focused extractor pass sharpens that fit again.
USECODE/EUSECODE_extracted/immortality_npctrig_clauses.mdnow showsNPCTRIGslot0x0aas a fixed-width five-clause ladder: subheaders at0x0064/0x0093/0x00c2/0x00f1/0x0120, uniform0x2fstride, backward-walking targets, and onebranch_3f_0a+push_24_51+writeback_57_02triple in each full clause. The new runtime-fit section also matters:000d:5572proves the extra word from0005:2c35is additive (entity_vm_slot_load_value(...) + offset), so slot0x0anow exposes the only surviving compact five-row selector family that plausibly matches byte A in000d:21ed, while slot0x20remains a one-clause typeNpc-heavy body with no comparable writeback/push motif or stride family. - The downstream-use follow-up weakens that direct selector fit. Instruction windows at
000d:47ef..47f3showentity_vm_context_create_from_slot_indexstoring slot indexSIat+0x32and the dynamic additive wordDIat+0x34, but the live sequencer lane000d:21ed -> 000d:22bcnever rereads either field: after the create call it only touches the copied blob at+0x102, the seeded byte lane at+0xd6/+0xd8, and the caller stream at+0xcc/+0xce. The persistent uses of+0x34are instead the object save/load path:000d:49e9..4a27serializes+0x10cthen+0x34, and000d:4c2d..4c4dreloads(+0x32,+0x34)throughentity_vm_slot_load_value_plus_offsetbefore storing the returned pair at+0x10c/+0x10e. The safest current read is thereforepersisted source offset feeding a later slot-value reload, notdirect clause selector consumed by the matrix stage, which weakens theNPCTRIG slot 0x0aalignment unless the derived reload value itself can still be tied back to that ladder.
entity_vm_opcode_sequence_run opcode-to-payload-shape matrix (sequencer-local)
| Sequencer stage | Code anchors | Opcode / lane status | Payload shape class | Verified behavior |
|---|---|---|---|---|
000d:0988 (entity_vm_opcode_mutate_referent_chain) |
000d:ec1d, 000d:0988 body |
Known 0x18..0x1b family |
Inline/indirect chain payloads | 0x18/0x19 append-unique and 0x1a/0x1b remove-matching over referent chains, with indirect-vs-inline mode split and shared epilogue. |
000d:177c |
000d:ebf5, 000d:178b..17aa |
Numeric opcode unresolved in this dispatcher lane | Word scalar (frame-local -> stream) | Does not read +0xd6/+0xd8; subtracts 2 from [context+0xcc] and pushes one frame-local word (BP-0x1c6) onto the stream stack. |
000d:1acb |
000d:ec09, 000d:1acb..1b22 |
Numeric opcode unresolved in this dispatcher lane | Word-pair/list consumer + boolean output | Reads one 32-bit pair from stream ([context+0xcc], then +4), compares against AX:DX, and pushes a 16-bit predicate result back to stream. |
000d:21ed -> 000d:22bc |
000d:21ed, 000d:22d2, 000d:22ee, 000d:2324..237b, 000d:23da..2421 |
Caller block + internal stage | Mixed: byte metadata + word id matrix | Consumes two signed bytes from seeded +0xd6/+0xd8 as shape/count metadata, then consumes streamed words as entity/link ids for entity_link; only non-0x0400 words are pushed back. |
000d:1d4a |
000d:ec48, 000d:1d4a |
Conditional substage when [obj+0xba]==0 |
Control/sentinel (no payload shape proven) | Current body is INT3-only (boundary suspect); treated as a control gate/trap island, not a verified payload transformer. |
000d:2104 |
000d:ec54, 000d:2104..212b |
Numeric opcode unresolved in this dispatcher lane | Mixed scalar/handle return | Writes result to caller out-ptr: path A stores frame-local dword (BP+0xfdaa/fdac), path B stores object word ([obj+2]) with high word cleared; then returns via opcode epilogue. |
Pass-4 dispatcher lane update (opcode selector evidence)
What is now hard evidence in code:
000d:0988compares one opcode-local word at[BP-0x32]against concrete values0x19,0x1a, and0x1b(000d:099b,000d:09a1,000d:0a07,000d:0a0d).entity_vm_opcode_sequence_run(000d:ebe3) calls000d:177c -> 000d:1acb -> 000d:0988 -> 000d:22bc -> optional 000d:1d4a -> 000d:2104(000d:ebf5,000d:ec09,000d:ec1d,000d:ec31,000d:ec48,000d:ec54).000d:177c,000d:1acb, and000d:2104do not contain their own opcode compares in recovered body ranges; they behave as wrapper stages around the opcode-local family tested in000d:0988.- The entry/exit contract is one step tighter too.
000d:ebe9seeds the first stage from object field+0xfe, while the success tail at000d:ec62..ec79runstracked_entity_handle_mark_remove_all_if_enabledand then gatesFUN_0008_ebe7on object field+0xc0plus byte+0x4b. So the sequencer is not just an isolated opcode cluster; it also participates in outer runtime cleanup and follow-up dispatch state.
Conservative case identity mapping from this pass:
000d:177c= pre-mutate stack push stage for the same[BP-0x32]family.000d:1acb= comparator stage (stream dword pair -> boolean word) for that family.000d:0988= concrete opcode discriminator for0x19/0x1a/0x1b(with0x18still implied by sibling path behavior).000d:2104= family finalizer writing mixed immediate/object output to caller out-ptr.
Still unresolved after this pass:
- The animation constructor near calls at
000e:283e,000e:2931, and000e:29e4land on a separate mis-split000e:ebe3region, not onentity_vm_opcode_sequence_run. They therefore no longer count as direct xref evidence for the000ddispatcher. - The true upstream selector/write path for
[BP-0x32]inentity_vm_opcode_sequence_runis still unresolved, and no additional opcode id can yet be assigned uniquely beyond the internal0x19/0x1a/0x1bfamily already proven inside000d:0988. - Repeated MCP-visible instruction and data-use searches still do not produce a real direct caller edge for
entity_vm_opcode_sequence_run,0005:2c35, or0005:2c68. For now that makes the next defensible routecaller-frame / shared-consumer recovery, not more recycled raw call searches or the retired000a:44fdand000e:ebe3hypotheses.
First readable VM IR sketch (verified-only)
From direct decompile/disassembly in 000d:0988, 000d:208b, 000d:21ed, 000d:22bc, and 0008:7d27, the current script-readable IR shape is:
APPEND_UNIQUE_INLINE(opcode 0x18, implied sibling in000d:0988)APPEND_UNIQUE_INDIRECT(opcode 0x19)REMOVE_MATCHING_INDIRECT(opcode 0x1a)REMOVE_MATCHING_INLINE(opcode 0x1b)MATERIALIZE_OR_FORWARD_VALUE(000d:208bpath afterentity_vm_context_create_from_slot_index)PUSH_FRAME_WORD_LITERAL(000d:177c: pushes one frame-local word to stream stack)COMPARE_STREAM_DWORD_AND_PUSH_BOOL(000d:1acb: consumes one stream dword pair and pushes predicate word)PREPEND_INLINE_PAYLOAD(000d:21ed: subtracts from context+0x102then copies caller bytes)BUILD_ENTITY_LINK_MATRIX(000d:22bc: two streamed dimension bytes, streamed id table, repeatedentity_linkcalls)FINALIZE_MIXED_VALUE_TO_OUTPTR(000d:2104: emits either immediate frame dword or object-word-derived value)EMIT_OR_PUSHBACK_RESULT(000d:22bctail: values without0x0400marker are pushed back to caller stream beforeentity_vm_opcode_finish)
Minimal pseudocode-style sketch:
referent = active_referent_id()
chain = referent.payload_chain
chain = mutate(chain, opcode_0x18_to_0x1b, payload_mode)
value = materialize_or_forward(context_from_slot(stream_state))
if opcode_lane == inline_payload: value = prepend_inline_payload_and_build_link_matrix(stream_ids)
emit(value)
This remains consistent with descriptor-side evidence: referent-only anchors (JELYHACK/JELYH2) can still drive behavior once neighboring event-capable descriptors attach payload/event semantics to the same referent island.
First readable pseudo-script renderings (verified-only)
entity_vm_context_create_from_slot_index adds one more readable anchor for this IR: after it seeds the embedded mini-VM from the runtime owner table at 0x6611 -> +0x1315/+0x1317 -> (+0x10/+0x12) + 0x0d*slot + 4, it also writes the same far source pair into the per-slot mirror row addressed through 0x39ca[context_slot]. That keeps the current readable model honest: the mirror is part of context creation for slot-backed VM state, not yet a proven standalone descriptor-dispatch cache.
The best verified human-readable form right now is therefore a small family of templates rather than a one-record-equals-one-opcode script dump.
Readable template A: referent anchor with event-bearing attachment (JELYHACK island)
anchor JELYHACK(referent)
anchor JELYH2(referent)
attach REE_BOOT(event, counter, item)
attach SFXTRIG(event)
optional_callback SURCAMEW(eventTrigger, link, code, screen, cameraEgg, trueRef, therma)
vm_effect:
chain = APPEND_UNIQUE_INLINE(...) or APPEND_UNIQUE_INDIRECT(...)
chain = REMOVE_MATCHING_INLINE(...) or REMOVE_MATCHING_INDIRECT(...)
value = MATERIALIZE_OR_FORWARD_VALUE(slot_backed_context)
if inline_payload_present:
payload = PREPEND_INLINE_PAYLOAD(caller_blob)
links = BUILD_ENTITY_LINK_MATRIX(shape_a, shape_b, entity_ids)
FINALIZE_MIXED_VALUE_TO_OUTPTR(value)
Why this is the current best readable rendering:
JELYHACKandJELYH2remain referent-only sibling descriptors with identical first-16-word header shape injelyhack_descriptor_compare.tsv.- The nearest event-bearing neighbors in
jelyhack_island_graph.mdareREE_BOOT(event),SURCAMEW(eventTrigger), andSFXTRIG(event), so the readable unit is better modeled asanchor + attachmentthan as a self-containedJELYHACKevent record. - The runtime side already supports exactly that shape: one referent anchor can own mutable payload chains, and the
000d:21ed -> 000d:22bcpath can expand one inline payload into an entity-link closure beforeentity_vm_opcode_finishcommits the result.
Readable template B: active event hub with trigger-side neighbors (EVENT island)
neighbor ROLL_NS(referent, item, item2, riderList, time, total, counter, oldz, cargo, zCheck, zMax)
attach COR_BOOT(event, counter, item)
attach EVENT(event, item, source, dest, door, link, time, counter, counter2, post1, post2, floor, flicMan)
attach NPCTRIG(event, item, item2, typeNpc)
neighbor CRUZTRIG(referent, item, elev)
neighbor NPC_ONLY(referent, item, link)
neighbor VMAIL(referent, textFile)
vm_effect:
select referent-bearing neighborhood
mutate referent payload chain via opcode 0x18..0x1b family
materialize slot-backed value or inline payload
if payload carries shape/count bytes:
build entity-link closure matrix from streamed ids
emit event-bearing result through shared opcode epilogue
Why this second template matters:
event_island_graph.mdandevent_descriptor_compare.tsvshow a compact three-node event-bearing core (COR_BOOT,EVENT,NPCTRIG) embedded inside referent/link/text neighbors, which matches the sameanchor/neighbor + attachmentruntime model seen aroundJELYHACK.EVENTis structurally richer than the_BOOTandNPCTRIGsatellites, so it reads better as a hub descriptor whose fields parameterize the same VM-side payload-chain and link-matrix machinery rather than as a flat peer row.- This is the first point where the binary descriptor artifacts and the
000dVM IR can be rendered together as a readable pseudo-script target without claiming a direct descriptor-id switch that the code still does not prove.
Wrapper mask-family expansion around 0005:2867-2d30
The next gameplay-side wrapper pass now extends well past the three earlier seed wrappers and shows one coherent local mask ladder around entity_vm_context_try_create_masked_for_entity.
Verified wrapper ladder
| Address | Mask pair | Extra pushed value | Verified caller / guard notes |
|---|---|---|---|
0005:27a4 |
0x0001:0000 |
none | Existing seed. Called from 000c:a09e on the entity +0x5b bit-0x0004 branch. |
0005:2867 |
0x0002:0001 |
none | Calls FUN_0005_2686 first, so the local entity id must be 1..255 when that gate matters. If seg030 helper FUN_0005_ffed reports true, the wrapper only continues when entity_class_get_flag8(local_id) is true or local_id == 1. Called at 000c:8b5b, 000c:8be2, 000c:8d59, 000c:8dec, 000c:9536, 000c:95ed, 000c:9868, and 000c:a007; the 000c:8b5b / 000c:a007 callers then store the returned word into entity field +0x39 before entity_state_tick_dispatch. |
0005:2918 |
0x0020:0005 |
CONCAT22(param_4,param_3) |
Sole current caller is 0006:43e5, reached only when caller object word +0x3c == 0x20b; it passes caller fields +0x36/+0x38 as one extra dword before the out pointer. |
0005:2ae2 |
0x0004:0002 |
none | Sole current caller is 0008:023d inside a dispatch-style loop body. |
0005:2c06 |
0x0200:0009 |
none | Adjacent simple wrapper in the same local family. |
0005:2c35 |
0x0400:000a |
sign-extended word argument | Adjacent simple wrapper; assembly pushes one extra sign-extended word before the out pointer. |
0005:2c68 |
0x0800:000b |
sign-extended word argument | Same pattern as 0005:2c35, with one extra sign-extended word operand. |
0005:2c9b |
0x0010:0004 |
none | Global gate wrapper: returns early unless 0x1056 != 0. |
0005:2cd2 |
0x1000:000c |
none | Adjacent simple wrapper in the same family. |
0005:2d01 |
0x4000:000e |
none | Adjacent simple wrapper in the same family. |
0005:2d30 |
0x8000:000f |
none | Larger gameplay gate. Sets entity class-word bit 0x2000 via `FUN_0005_2745(entity, class_word |
Shared preconditions and what they imply
- This island is firmly gameplay-side, not a descriptor-id switch. The wrappers consume live entity/object far pointers, use the runtime slot mapper at
000d:45c5, and gate on entity-id range, entity class word bits, class-record bytes from0x7e46, and state bytes such as entity+0x5b,+0x32, and+0x39. - The local ladder is not random. The mask pairs now cover
0x0001:0000,0x0002:0001,0x0004:0002,0x0010:0004,0x0020:0005,0x0200:0009,0x0400:000a,0x0800:000b,0x1000:000c,0x4000:000e, and0x8000:000f, which reads like one sparse owner-side slot taxonomy rather than one-off wrappers. 0005:2918,0005:2c35, and0005:2c68are especially useful because they push extra payload words before the out pointer. That shape fits the current VM model ofslot-selected context + caller-provided payload datamore naturally than a pure referent-anchor lookup.0005:2d30is the strongest new caller-side anchor. Its branch structure is about class/state gating, dispatch-entry emission, and gameplay-object cleanup/state changes before the masked VM call, which is a better behavioral match for active-event or trigger-bearing descriptors than for a passive referent anchor.
Current attribution after the wrapper pass
- The wrapper family now fits the readable active-event template better than the narrow
JELYHACKreferent-anchor template. The callers are dominated by gameplay state checks, class-table gating, dispatch-entry emission, and object-state writes; that is closer toEVENT/NPCTRIG/_BOOTstyle active-event ecosystems than to a record whose only verified descriptor-side field isreferent. - This does not overturn the existing JELYHACK model.
JELYHACK/JELYH2still fit best as referent anchors that can feed the VM referent registry, while neighboring event-bearing descriptors can attach behavior to the same island. - The direct descriptor bridge is still unproven. No code path in this wrapper family reads an explicit EUSECODE class id or a
69:0A00 eventversus24:0A02 eventTriggertag, so the result stays at ecosystem-level correlation rather than a hard descriptor-class rename.
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, and0007:814e/822e -> 0005:2d01. - The two direct
0005:2d30callers are now role-shaped as well:0005:5370reaches slot0x0fonly afterentity_class_has_flag2000succeeds and class-word bit0x8000is clear, while0005:6f47reaches the same gate from the complementary branch where class-word bit0x2000is still clear before the caller continues into its larger state/update flow. 0005:2c68is no longer usable as indirect selector evidence. The0007:e521and0007:e73cinstruction windows do push0x2c68immediately beforeCALLF 000a:44fd, but decompile now shows that value is the caller-local data pointerDAT_0000_2c68passed into a fatal-report helper, not an indirect call to wrapper0005:2c68.0005:2c35and0005:2c68therefore both remain unresolved in direct caller/xref evidence, and the real selector work stays centered on the still-xref-dark upstream edge intoentity_vm_opcode_sequence_runrather than the disproven000a:44fdhypothesis.- 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.
Current batch: masked-context hub and sequencer-internal consumer recovery
- The generic masked VM-context hub is now instruction-verified at
000d:463a. That body maps the incoming entity throughentity_vm_slot_index_from_entity, rejects the path when runtime global0x6610is active or the owner/resource table at0x6611 + 0x1315/+0x1317is absent, tests the per-slot0x0d-stride owner mask pair against the caller-supplied high/low mask words, and only then falls intoentity_vm_context_create_from_slot_index(000d:46ec). search_instructionson000d:463anow confirms this hub is not isolated to the0005wrapper island. In addition to the known seg021 wrappers, live direct callers now include0004:f047(mask0x8000:0x0007),0004:f076(mask0x2000:0x0015), and larger callers at0006:0bbc/0006:10e7. That is new caller-side evidence for the wider owner-slot taxonomy even though the offset-specialized wrappers0005:2c35and0005:2c68themselves still have no direct caller edges.- The xref-dark offset wrappers are now tighter structurally too. Disassembly of
0005:2c35and0005:2c68confirms they do nothing beyond sign-extending one extra word, passing mask pairs0x0400:0x000aand0x0800:0x000b, forwarding the entity pointer to000d:463a, and returning the out-word on success. That keeps their best current reading atoffset-specialized masked context creation, not a separate selector lane. - The offset word is now behaviorally tighter too.
entity_vm_slot_load_value_plus_offset(000d:5572) is a straightentity_vm_slot_load_value(...) + offsetwrapper, so the extra word passed by0005:2c35is not a second mask or opaque cookie; it is an additive selector/value adjustment that can plausibly choose one of the evenly spaced slot-0x0aclause starts once a real caller is recovered. - The next caller-path pass tightens why
0005:2c35stays dark. MCP xrefs now show only three entries intoentity_vm_context_create_from_slot_index(000d:46acfrom the generic masked hub, plus direct internal sequencer islands000d:208band000d:21ed), while0005:2c35itself still has no recovered code or data xrefs. Stack setup at000d:208bhardcodes the000d:5572additive slot-load parameter to0, which does not match theNPCTRIGslot-0x0aclause starts (0x0064/0x0093/0x00c2/0x00f1/0x0120) or backward targets (0x00db/0x00ac/0x007d/0x004e/0x001f). The remaining live selector frontier is therefore the still-overlapped000d:21edcaller frame, not a normal visible caller of0005:2c35. - The sequencer lane also gained two concrete internal consumer shapes.
000d:208bis now the instruction-verifiedcreate one slot-backed context and materialize or forward its resultpath: it builds a0x6714context from the caller stream state, writes immediate-flagged results straight to the out pointer, and otherwise forwards the created object throughentity_vm_opcode_finish.000d:21edis the matchingprepend inline payload and build entity-link matrixpath: it creates a context, prepends caller-owned bytes into+0x102, consumes the seeded+0xd6/+0xd8bytes as shape/count metadata, and builds repeatedentity_linkclosures from the following streamed ids before the same finish path. - A new downstream-use pass narrows the extra-word role further. The stored offset field at context
+0x34is now confirmed as durable object state rather than an immediate sequencer input:000d:21ed -> 000d:22bcdoes not reread it at all,000d:498f/000d:4a78serialize and reload it, and000d:4c2d..4c4drecomputes a slot-backed value from(+0x32,+0x34)into+0x10c/+0x10e. That shifts the remaining immortality question one step downstream: ifNPCTRIG slot 0x0astill fits this runtime lane, it is more likely through the value reloaded from the slot-plus-offset pair than through+0x34as a direct clause selector. - The hidden pre-call span in the
000d:21edlane is now recovered from direct program-memory bytes as well. Window000d:2131..21edreads the seeded+0xd6/+0xd8stream as three successive words followed by two signed bytes: word0 becomes the slot index pushed at000d:21d4, word1 and word2 are added at000d:21d0before being pushed as the dynamic additive arg at000d:21d3, byte3 is forwarded as the setup-data length byte, and byte4 becomes the inline-blob length used for the later prepend copy. That makes the source classification explicit: context+0x34is not loaded from the owner table or from the caller object at+0xd4; it is a computed sum of two consecutive words inside the seeded stream itself. - The same recovered window also tightens the upstream source layout feeding
entity_vm_context_setup. The current caller frame base iscaller + [caller+0xd4], where+0xd4matches the saved frame offset written byentity_vm_stack_push_frame(000c:f7c7) rather than a descriptor-local field. From that frame base,000d:21db..21e0pushes[frame+0x0a/+0x0c]as a far pointer passed intoentity_vm_context_setup, and000d:21bd..21c8separately derives[frame+0x0e]as the inline payload tail copied after context creation. So this consumer is now better modeled as one generic VM frame-record shape with two payload sources: a frame-stored far pointer plus byte-sized setup length for the initial+0xccstack seed, followed by an adjacent inline tail blob with its own byte-sized length. - The next frame-producer pass recovers the closest non-overlapped writer feeding that lane too. Raw bytes at
000c:fbf7..fc47(caseD_0) show a generic frame-record producer reading one signed placement byte from the same seeded+0xd6/+0xd8stream, popping a far-pointer dword from the caller stream at[caller+0xcc/+0xce], computingframe_base = caller + [caller+0xd4], and storing the dword at[frame_base + placement + 0x4/+0x6]. That means the immediate source far pointer consumed later by000d:21edis already stream-backed rather than owner-row-backed; if the000d:21edrecord uses this exact producer family for its[frame+0x0a/+0x0c]lane, the relevant placement byte is0x0006, which is the only value that lands the written dword at+0x0a/+0x0cand leaves the inline tail starting at+0x0e. - That stronger runtime shape weakens any claim that
000d:21edis already reading a descriptor-family-specific record.NPCTRIGslot0x0astill remains the best surviving descriptor-side candidate because its five-clause ladder is the only compact body that fits the row-count frontier, but the code evidence now shows the immediate input to000d:21edis a generic frame-local record containing a source far pointer, a seeded slot/additive pair, and an inline tail. The remaining descriptor-side question is therefore one level earlier again: where the caller frame receives its[frame+0x0a/+0x0c]far pointer and whether the summedadd_a + add_bstill corresponds to a clause-base/delta pair insideNPCTRIGslot0x0arather than to a more generic descriptor-relative offset. - That changes the
NPCTRIGcross-check in one important way.NPCTRIGslot0x0aremains the strongest surviving descriptor-side hypothesis only as an upstream source for a predecoded caller-stream record, because the recovered writer consumes a caller-stream dword plus a seeded placement byte instead of indexing owner rows or descriptor tables directly.NPCTRIGslot0x20still reads as the typed/setup companion body, but neither slot is now a good fit for the immediate write into[frame+0x0a/+0x0c]itself. - One more layer of the producer path is now instruction-verified too. The setup call at
000d:4788 -> 000c:f844 -> 000c:f6e8does not seed the new context's+0xcc/+0xcecaller stream directly from the owner table row. Insteadentity_vm_context_setupfirst allocates or reuses the object-local stream buffer atcontext+0x36+0xcc, then copies a caller-supplied setup blob from the parent frame using the far pointer/length arguments passed through000d:46ec. The slot/additive record returned byentity_vm_slot_load_value_plus_offsetbecomes the separate seeded+0xd6/+0xd8stream, while the owner-table row at(+0x10/+0x12) + 0x0d*slot + 4is mirrored to0x39ca[slot]and preserved separately in the context state. - The closest sibling template to
caseD_0also sharpens the placement-byte reading.000c:ff9f..000d:000dreads one signed placement byte and one length byte from the same seeded+0xd6/+0xd8stream, then copieslenbytes from[frame_base + placement + 0x4]back onto the caller stream. Together with the recovered000d:21edconsumer layout ([frame+0x0a/+0x0c]far ptr,[frame+0x0e..]inline tail), that makes the strongest current fit a fixed two-slot family for this record shape:caseD_0uses placement0x0006for the far-pointer dword, and the sibling blob-copier uses placement0x000afor the inline tail starting atframe+0x0e. - The producer side of that same record family is now tighter too. Linear raw-byte recovery across
000c:f98b..000d:000dshows000c:fc4b..fcbbas the forward blob producer matching the reverse000c:ff9f..000d:000dcase: it reads placement and length from the seeded+0xd6/+0xd8lane, computesframe_base = caller + [caller+0xd4], and copieslenbytes from the caller stream at[caller+0xcc/+0xce]into[frame_base + placement + 0x4]. For the000d:21edrecord shape, that makes placement0x000athe best fit for the inline tail now consumed from[frame+0x0e..]. - The dword lane now has a matching reverse case as well. Raw bytes at
000c:ff1f..ff83show the same recursive family in the opposite direction: it reads one signed placement byte from the seeded+0xd6/+0xd8lane, computesframe_base = caller + [caller+0xd4], loads a dword from[frame_base + placement + 0x4/+0x6], subtracts4from[caller+0xcc], and writes that dword back onto the caller stream. In other words, the immediate upstream producer for the000c:fbf7..fc47far-pointer write can already be another frame-record copier, not a direct owner-row or descriptor-table lookup. - That narrows the remaining source classification again. The setup far pointer consumed by
000d:21edis now best modeled as a recursively propagated pointer into another VM-side byte buffer or predecoded descriptor workspace, not as the owner/resource row source mirrored separately through0x39ca. The owner row still matters for slot-backed state reloads, but theentity_vm_context_setupblob pointer itself is traveling through the frame-record family independently of that owner-row mirror. - That also weakens the full-tuple
NPCTRIGfit one more notch without killing it. The surviving tuple is now better read as(slot, add_a, add_b, setup_len, inline_len, placement=0x0006/0x000a)feeding a generic recursive frame-record contract.NPCTRIGslot0x0aremains the strongest descriptor-side candidate only as an earlier decoder that could have produced this predecoded record family, while slot0x20still reads as the typed/setup companion body. No recovered instruction in the immediate000c:f98b..000d:000dfamily yet ties the setup far pointer directly back to either slot. - Net effect on source classification: the
000d:21ed-relevant frame record is still not best modeled as generic VM scratch. Its immediate setup bytes are recursively copied from a parent frame record, and the wider context-build path is still anchored in descriptor-derived VM state (+0xd6/+0xd8fromentity_vm_slot_load_value_plus_offset, owner-row source mirrored via0x39ca). What remains open is not whether this lane is scratch-backed, but which earlier decoder materializes the parent-frame far pointer before000c:fbf7consumes the next dword. - After the new reverse-case recovery, that blocker can be stated more tightly: the missing piece is no longer a generic parent-frame materializer somewhere above
000c:fbf7, but the first non-recursive decoder that originates the far pointer before theff1f/ff9f -> fbf7/fc4b -> 000d:21edpropagation chain repeats it. - The next pass closes that specific source-classification gap inside the same hidden interpreter body. Raw bytes at
000c:fa2f..fa5brecover an inner opcode dispatcher that reads one opcode byte from the seeded+0xd6/+0xd8lane, bounds-checks it against0x79, and jumps throughCS:[0x3d9f + opcode * 2]. That matters because the same local case family now exposes both the recursive frame-record replay stages and a separate set of direct caller-stream seed cases. - Those non-recursive seed cases are now concrete.
000c:fd51writes one inline byte from the+0xd6/+0xd8control stream onto the caller stream after decrementing[caller+0xcc]by1,000c:fd91and000c:fdd1do the same for inline words, and000c:fe11..fe59does it for an inline dword. In the dword case the interpreter advances through four literal bytes in the control stream, subtracts4from[caller+0xcc], and writes the literal dword directly onto the caller stream before any frame replay logic runs. - That makes
000c:fe11the strongest current first non-recursive origin for the far-pointer lane later consumed by000c:fbf7..fc47and then by000d:21ed. The immediate setup far pointer is therefore no longer best modeled as coming from the owner/resource row, the mirrored0x39calane, or a generic VM scratch buffer. Its immediate compiled-side source is an inline dword literal embedded in the interpreter/control stream itself;000c:ff1f..ff83and000c:fbf7..fc47are replay stages layered on top of that literal-seeding path. - That retunes the
NPCTRIGcross-check again without killing it.NPCTRIGslot0x0astill remains the best upstream descriptor-side candidate because it is still the only compact active-event body that fits the surviving slot/additive shape, and slot0x20still reads as the typed/setup companion. But any direct immortality mapping now has to explain how the upstream decoder turns that descriptor family into a literal-bearing VM control stream before000c:fe11, not how000d:21edor000c:fbf7index descriptor rows directly. - One more pass tightens the creator/consumer split enough to rule out the owner row as the immediate control-stream builder. Direct instruction recovery at
000d:46ecshowsentity_vm_context_create_from_slot_indexusing the owner-table row(+0x10/+0x12) + 0x0d*slot + 4only for the separate0x39ca[slot]mirror, while the live+0xd6/+0xd8lane passed intoentity_vm_context_setupstill comes fromentity_vm_slot_load_value_plus_offset. In the recovered000d:21edpre-call span, that seeded lane is consumed asword slot_index,word add_a,word add_b,byte setup_len,byte inline_len, withadd_a + add_bforwarded as the dynamic word stored at context+0x34. - The same pass also clarifies the setup-payload contract that feeds the later link-matrix stage.
000d:21edpasses[frame+0x0a/+0x0c]as the setup far pointer intoentity_vm_context_setup, copies[frame+0x0e..]as a separate inline tail, and then000d:22bcconsumes two signed metadata bytes plus a streamed word matrix to drive repeatedentity_linkcalls. The immediate source is thereforedecoded per-slot VM stream + frame replay, notowner-row lookup + direct descriptor row. - That changes the opcode-family reading around
000c:fa2fin a useful way even though the exact opcode indices remain unresolved in the current overlapped table view. The hidden dispatcher now has a verified immediate-literal family:000c:fd51pushes one inline byte to the caller stream,000c:fd91pushes a sign-extended byte as a word,000c:fdd1pushes an inline word, and000c:fe11pushes an inline dword. Together with the recursive replay cases000c:ff1fand000c:ff9f, that is enough to classify the upstream builder as a generic literal-bearing interpreter/control stream rather than a directNPCTRIGclause reader. - The descriptor-side fit therefore weakens from
specific direct NPCTRIG selectortobroader descriptor-derived VM workspacewhile staying narrow enough to keepNPCTRIGslot0x0aalive as the best upstream candidate. Slot0x0astill matches the event-bearing compact body and its five-clause ladder remains the only surviving compact source family with a plausible row-count/additive shape, but slot0x20still looks like the typed/setup companion and neither slot is now a good fit for the immediate control-stream seeding logic itself. - The slot-load miss path now closes the workspace-materialization side of that question. Inside
entity_vm_slot_load_value(000d:51fd), a cache miss triggers000d:5066, which first reads a slot header and then acount * 6 + 0xc0subentry table through the owner-resource wrapper000d:714c. When one subentry is still unloaded,000d:5305..53d4allocates a value object through000d:3800, then calls000d:714cagain with the subentry source range and the new object's buffer at+0x0a/+0x0c; the function returns that same buffer pointer as the finalDX:AXresult. The immediate+0xd6/+0xd8workspace is therefore first materialized as a file-backed slot-value buffer during the slot-load miss path itself, not synthesized later from the owner-row mirror or from generic scratch state. - The inline-tail source is not as tightly closed yet. The same hidden case family contains several immediate scalar caller-stream seed cases, so the
000d:21edtail at[frame+0x0e..]can now plausibly be assembled from control-stream literals or from another nearby non-recursive payload case rather than from a direct owner-row read. No instruction recovered in000c:f98b..000d:000dperforms a matching direct descriptor-row lookup for that tail. - Net effect from this pass: the missing outer selector into
entity_vm_opcode_sequence_runis still unresolved, but the lane is no longer just one opaque dispatcher plus dark wrappers. It now has a verified generic masked-context creation hub, wider caller-family anchors for that hub, and two internally differentiated sequencer consumer blocks built directly onentity_vm_context_create_from_slot_index.
Follow-up: four newly surfaced direct 000d:463a callers
0004:f033(0x8000:0x0007) now reads as a generic gameplay-side materialization lane rather than a state-transition helper. When the local seg021 class-nibble query returns8, the wrapper bypasses the VM path and returns object word+0x02directly from the locally produced object. Otherwise it forwards throughentity_vm_context_try_create_masked_for_entityand returns the created object's word+0x02on success.0004:f05c(0x2000:0x0015) stays on the gameplay-state side too, but with a stronger caller role. The only current direct caller window at0004:f2b3reaches it after overlap/proximity tests and entity byte+0x32toggling, so the safest reading is stillstateful gameplay materialization lane, notdescriptor selector.entity_vm_context_try_create_mask_0008_slot30_with_offset(0006:0ba4) adds the first strong non-0005extra-payload lane. It passes mask0x0008:0x0030plus one caller word into000d:463a; on failure it drops into0006:0cfa, which copies class-detail word+0x02to+0x04, derives a replacement selector from class-detail words+0x06/+0x08/+0x0aor the caller value, may clear flag0x08throughentity_class_clear_flag8_and_dispatch, and then continues into the local state-transition/dispatch table. That is concrete evidence that at least one extra-word masked lane is feeding class-state transition materialization rather than a free-standing VM selector root.entity_vm_context_try_create_mask_0010_slot08_with_offset_if_ready(0006:108c) provides the second strong extra-payload lane. It passes mask0x0010:0x0008plus one caller word into000d:463a, but only after local readiness gates through0006:ffedplus the seg021 availability/flag8-clear path. Unlike the earlier looser reading, the helper itself does not fall back to0006:13b0or0006:13e4; on miss it simply returns0. That makes the function a guarded masked-materialization attempt, while the neighboring0006:13b0/13e4 -> 0006:07c0class-linked lookups remain adjacent family evidence rather than a direct local fallback inside0006:108c.- Taken together, the new seg004 and seg006 callers strengthen the existing read of the still-dark wrappers
0005:2c35(0x0400:0x000a) and0005:2c68(0x0800:0x000b). Those wrappers still have no direct caller evidence, but they now sit inside a larger verified subfamily ofextra-word masked materializerswhose known members feed state selectors, class-linked values, or other gameplay-side payload resolution instead of acting as the real upstream selector intoentity_vm_opcode_sequence_run. - MCP-native function xrefs now reinforce that stopping point rather than changing it:
entity_vm_context_try_create_masked_for_entityreports the expected direct callers through0004:f047,0004:f076, the named0005wrapper island, and the two seg006 callsites0006:0bbc/0006:10e7, whileentity_vm_opcode_sequence_runplus the dark0x0400/0x000aand0x0800/0x000bwrappers still surface no direct function-xref callers in the current database. The best next path therefore remains caller-frame recovery or nearby unnamed-function repair, not another generic masked-hub sweep.
| 000c:f844 | entity_vm_context_setup | Calls entity_vm_stack_init_with_data, then sets +0xd6..+0xe3 with position/dimension/state params |
| 000c:f600 | entity_vm_pair_stack_push | Push (word_a, word_b) onto 31-entry array at [ptr+0x80] (count); error if full |
| 000c:f63c | entity_vm_pair_stack_pop | Pop and return word from pair stack; error if empty |
| 000c:f94f | entity_vm_counter_add | [ptr+0xd6] += param_2; simple accumulator |
| 000c:f95f | entity_vm_set_value_from_slot_plus_offset | Calls entity_vm_slot_load_value_plus_offset and writes the resulting 32-bit value into [ptr+0xd6/+0xd8] |
| 000c:f98b | entity_vm_set_field_da_to_global | Writes [param_2+0xda far-ptr + 2] into [0x8c94] |
VM field offsets: +0xcc=VM stack ptr, +0xce/+0xd0=segment regs, +0xd2=base, +0xd4=frame depth, +0xd6/+0xd8=32-bit VM value/counter lane, +0xda/+0xdc=additional VM pointer/bounds lane. The 200-byte body region at [ptr+4..+0xcc] holds 100 words of script/state payload. The pair-stack (field +0x80) is separate — likely pass/return value stack for the mini-script.
Raw 000c Cursor Zone / Slot Array / String Queue Batch
Cursor / Directional Zone Classifier
| Address | Name | Evidence |
|---|---|---|
000c:e6d9 |
cursor_zone_quadrant_classify |
Splits screen by [0x63d6]/2 and [0x63d8]/2 vs bounds [0x8c6c..0x8c72]; returns directional code from 9-word table at 0x6401 |
Zone table layout (9 entries): NW/N/NE / W/Center/E / SW/S/SE based on horizontal threshold at 0x8c6c/0x8c70 and vertical at 0x8c6e/0x8c72.
Slot Array System
A complete 29-slot menu/choice array with fixed stride 0x15 bytes, base at [ptr+0x67], count at [ptr+0x7a]:
| Address | Name | Evidence |
|---|---|---|
000c:ea53 |
entity_slot_count_update_and_notify |
Sets [ptr+0x72]=param-1; calls slot_array_get_current_entry and slot_array_find_and_dispatch; calls vtable[0]() when +0x75 flags set |
000c:eba5 |
slot_array_dispatch_matching |
Walks 0xb-stride array from [ptr+4]; calls thunk for each entry where [entry+9]==param_4 |
000c:ec30 |
slot_array_dispatch_if_nonempty |
Returns 0xffff if count < 1; else dispatches |
000c:ec9e |
slot_array_find_and_dispatch |
Searches 0xb-stride array for [entry+9]==param_4; calls thunk on first match |
000c:ecf5 |
slot_array_push_entry |
Copies named string to [base+0xc]; writes 6 param words at +0x12..0x20; increments count |
000c:edb0 |
slot_array_push_raw |
Copies 0x15-byte raw entry from param_2; increments count |
000c:edf7 |
slot_array_pop |
Decrements [ptr+0x7a]; asserts >= 0 |
000c:ee19 |
slot_array_init |
Sets [ptr+0x78]=0, [ptr+0x76]=0, [ptr+0x75]=1 (active flag) |
000c:ee32 |
slot_array_clear_flags |
Clears [ptr+0x74]=0, [ptr+0x75]=0 |
000c:ee44 |
slot_array_get_current_entry |
Returns ptr + [ptr+0x7a]*0x15 + 0x67 (current entry ptr); 0 if count <= 0 |
String Queue
| Address | Name | Evidence |
|---|---|---|
000c:eadd |
string_queue_push |
Appends string to 10-entry queue at [ptr+4]; count at [ptr+2]; sets [ptr+0xd]=param_4 |
Additional VM-Adjacent Helpers
| Address | Name | Evidence |
|---|---|---|
000c:f2e7 |
entity_call_vtable_entry_10_if_valid |
Null-guard: calls (*[ptr+8+0x10])() if param_1 non-null |
000c:f39f |
string_table_lookup |
Searches [0x65bc/0x65be] table by key string; returns matching words to out-params |
Raw 000c Cursor Nav Dispatcher / State Reset Batch
Cursor navigation subsystem in 000c:d3e9–000c:db68. Manages directional zone changes, mouse button reads, keyboard scancodes, and entity vtable dispatch for interactive UI cursors.
Cursor Navigation Fields (entity object offsets)
| Offset | Purpose |
|---|---|
+0x32 |
Current zone code (0–8) |
+0x33 |
Previous zone code |
+0x37–+0x3a |
Directional booleans: N/S/W/E |
+0x3f–+0x42 |
Mouse button flags |
+0x45 |
Last keyboard scancode |
+0x47 |
Navigation index |
Globals: [0x63da] = mouse button state, [0x63d6]/[0x63d8] = cursor X/Y, [0x638e] and [0x6346] = reference data tables.
| Address | Name | Evidence |
|---|---|---|
000c:dac1 |
cursor_nav_state_reset |
Zeros all directional/button flags; sets [+0x32/+0x33]=0xff, [+0x47]=0xffff |