- 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.
55 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 |
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 |
000c:b153 |
entity_state_animation_done_tick |
Checks [param_2+0x14+0xa] animation-complete flag; if zero increments field49 and calls entity_state_check_field49_and_call_vfunc3c; if set calls vtable[0x3c] |
000c:b199 |
entity_state_input_key_handler |
Full input dispatcher: ESC/x/X → vtable[0x3c] (cancel); Left/Right arrows 0x14b/0x148 → prev state; n/N/0x14d/0x150 → next state; e/E → set field47=1; - with counter → trigger at 4. Manages field47 and field49 |
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= state-sequence index; 0=reset, 2=vtable callback, 4=triggered endfield47= keystroke-combo counterfield3f= 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_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 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+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. 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 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. - 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 byte-sized metadata fields shape the matrix walk and word entries are link/entity ids. - 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.
FUN_000d_ebe3 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).FUN_000d_ebe3calls000d: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.
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 onFUN_000d_ebe3. They therefore no longer count as direct xref evidence for the000ddispatcher. - The true upstream selector/write path for
[BP-0x32]inFUN_000d_ebe3is 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
FUN_000d_ebe3,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 intoFUN_000d_ebe3rather 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.
| 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 |