- Implemented a Python script to extract data from the EUSECODE.FLX file format. - Defined data structures for candidate entries and extracted chunks using dataclasses. - Added functions to read and parse the FLX table, extract candidate data, and generate human-readable output files. - Included functionality for analyzing extracted data, including generating summaries, descriptors, and event family reports. - Implemented utilities for calculating printable ratios, zero ratios, and identifying text-like data. - Added support for writing various output formats, including JSON, TSV, and Markdown.
32 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 [DAT_0000_6828 ptr + 0x40] = 1 then calls thunk unconditionally; enables global entity flag |
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]= another global entity far pointer- 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), andentity_vm_slot_load_valuecontains a verifiedPUSH 0x410path at000d:5290before calling the unresolved seg091 event/abort lane at000a:44fd.- This is not enough yet to say that
entity_vm_set_value_from_slot_plus_offsetis the immortality trigger, but it does show that the000cmini-VM / record-player cluster can hand work directly into a000dhelper that emits event0x410. - 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.- 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. - 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. |000c:f844|entity_vm_context_setup| Callsentity_vm_stack_init_with_data, then sets+0xd6..+0xe3with 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| Callsentity_vm_slot_load_value_plus_offsetand 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 |