# Raw 000e: Parser & RIFF/Animation Clusters Content extracted from `crusader_decompilation_notes.md`. Covers the `000e:` segment parser helper cluster and the RIFF/AVI animation streaming subsystem. --- ## Raw 000e Parser Helper Cluster A small helper cluster in the raw `000e:` area implements a fixed-size CRLF record parser/table builder, likely used by startup/config or script-ish text data. ### Newly renamed helpers | Address | Name | |---------|------| | `000e:345e` | `record_table_init` | | `000e:34cc` | `record_table_destroy` | | `000e:35c6` | `record_table_release_buffer` | | `000e:35ef` | `record_table_next_slot` | | `000e:3639` | `record_table_parse_buffer` | | `000e:3798` | `record_parser_read_line` | | `000e:38a0` | `record_parser_seek_next_marker` | | `000e:38f8` | `record_parser_find_marker` | | `000e:39cc` | `record_parser_dispatch_at_directive` | ### Behavior notes - `record_table_init` clears the table header and zeroes 300 words of inline storage. - `record_table_parse_buffer` walks a CRLF-separated text buffer, captures each line, splits around a marker helper path, and stores parsed entry state into `0x0c`-byte records. - `record_parser_read_line` advances to the next CRLF-delimited line, rejects lines that start with `@` or with non-identifier punctuation, and terminates the line in-place with `0`. - `record_parser_seek_next_marker` updates the parser's current marker cursor at `+0x18/+0x1a` by calling `record_parser_find_marker`; returns `1` if another marker was found, `0` at end-of-data. - `record_parser_find_marker` scans forward until an `@` marker or end-of-data; optionally consumes the remaining length from the parser state. - `record_parser_dispatch_at_directive` returns `0` unless the current substring begins with `@`; in the `@` case, it advances by 7 bytes and dispatches through a FAR thunk (`0000:ffff`). ### EUSECODE.FLX extraction notes - `USECODE/EUSECODE.FLX` does not look like a loadable code image or plain text script. It is now validated as an indexed binary container. - Current table model: - entry count at file offset `0x54` - entry table at `0x80` - 8-byte records: `` - `entry_count = 3074` - `table_end = 0x6090`, which matches the first non-zero payload offset - `403` non-zero entries in the current file - `tools/extract_eusecode_flx.py` now parses the full validated table and emits all `403` non-zero entries under `USECODE/EUSECODE_extracted/`, including `entry_index.tsv`, `descriptor_index.tsv`, `descriptor_neighborhoods.tsv`, `summary.json`, per-chunk `.bin`, and `.strings.txt` sidecars. - The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs: `class_layout_index.tsv` records `object_index`, `class_id`, the raw bytes-`8..11` field, derived `code_base_minus_one`, and `conservative_event_count`, while `class_event_index.tsv` expands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, and raw code-offset dwords. - The generated reports now expose lightweight descriptor summaries (`primary_label`, `field_names`, `field_tags`) so the object lane can be searched by field grammar instead of only by raw names. - The extracted data now separates into at least two lanes: - text-heavy records that fit the `000e:` CRLF parser model, such as `DATALINK` mission/objective text and `TEXTFIL1` message banks - binary object/behavior descriptors whose sidecars expose object names and field names, such as `EVENT`, `NPCTRIG`, `CRUZTRIG`, `TRIGPAD`, `JELYHACK`, `JELYH2`, `SPECIAL`, `SURCAMNS`, and `SURCAMEW` - The descriptor lane also shows a repeatable tagged field trailer rather than raw trailing strings only. Current spot-checks show patterns like `69 xx 00 ` and `24 xx 02 ` immediately before field names in `NPCTRIG`, `CRUZTRIG`, `TRIGPAD`, `SPECIAL`, and `SFXTRIG`. This is strong evidence that the field names belong to compact per-field metadata records, not accidental string leakage. - The strongest currently stable tag readings are: - `69:0000 -> referent` - `69:0A00 -> event` on event-capable classes such as `EVENT`, `NPCTRIG`, `COR_BOOT`, `REE_BOOT`, `SFXTRIG`, `FLAMEBOX`, `NOSTRIL`, `VAR_BOOT`, and `STEAMBOX` - `24:FE02` / `24:FC02` / `24:FA02` on object-reference-like fields such as `item`, `elev`, `door`, `source`, `dest`, `monster1`, `deadGuy`, and related referent-style links - `24:0A02 -> eventTrigger` on `SURCAMNS` / `SURCAMEW` - The tag report is not a full type system yet, but it is already enough to separate scalar/event slots from pointer-like object links in many descriptor classes. - Confirmed descriptor examples from the full index: - `EVENT`: `referent,event,item,source,dest,door,counter,counter2,link,time,post1,post2,floor,flicMan` - `NPCTRIG`: `referent,event,item,item2,typeNpc` - `CRUZTRIG`: `referent,item,elev` - `TRIGPAD`: `referent,item,elev` - `JELYHACK`: `referent` - `JELYH2`: `referent` - `SURCAMNS` / `SURCAMEW`: `referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun` - Current immortality-lane status inside EUSECODE: - the trigger/object namespace now clearly includes `JELYHACK`, `NPCTRIG`, `CRUZTRIG`, and `TRIGPAD` - `JELYHACK` / `JELYH2` sit in a local extraction neighborhood beside `SPECIAL`, `TRIGPAD`, `DATALINK`, `HOFFMAN`, `REE_BOOT`, `SURCAMEW`, and `SFXTRIG`, which looks more like a map/object grouping than random table order - that neighborhood does not make `JELYHACK` itself event-bearing, but it does place it immediately beside multiple event-capable or trigger-adjacent classes (`REE_BOOT`, `SFXTRIG`, `SURCAMEW.eventTrigger`) - no extracted chunk has yet been tied directly to event `0x410` - one exact `0x410` collision in compiled code is now explained away: `000e:0953` pushes `0x410` into imported `ASYLUM.27` from the animation audio-subframe path immediately after setting the local audio-completion byte at `+0xef1`. Since `ASYLUM.DLL` is the `ASS_*` audio/media library, treat this as a media ordinal/value collision rather than a second gameplay or USECODE event source. - the present best reading is that `0x410` is likely carried by data relationships between generic event-capable descriptors (`EVENT`, `NPCTRIG`, `SFXTRIG`, etc.) and map/object references rather than by a plain-text script line - The `000e:` record parser helpers still matter, but they now appear to cover only the text-oriented subset rather than the entire FLX payload. The strongest concrete caller so far is the raw window at `000e:1b9f..1d49`, where `record_table_parse_buffer` is invoked after setup of fields that match the known animation object layout (`+0x117/+0x11b/+0x11f/+0x123`, `+0xeaf/+0xeb1`, `+0x10f/+0x111`). That makes the currently verified `000e:3639` consumer part of the animation-object lane, not a clean standalone EUSECODE loader. - This shifts the current working model: treat `record_table_parse_buffer` as a text/metadata helper used by at least one animation/resource object, while the EUSECODE binary descriptor lane is more likely consumed by the `000d` VM/object interpreter path. - That `000d` path is now materially less anonymous: - the global runtime object at `0x6611` is now named `entity_vm_runtime_create` / `entity_vm_runtime_init_slots` / `entity_vm_runtime_release_slots` / `entity_vm_runtime_destroy` - it owns the 0x80-entry slot table and a retained owner/resource object at `+0x1315/+0x1317` - `entity_vm_slot_index_from_entity` and `entity_vm_context_try_create_masked_for_entity` show that gameplay entities are filtered through one owner-side slot-mask table before a context is created - `entity_vm_context_try_create_masked_for_entity` is now better constrained too: after the owner-side mask check succeeds, an immediate-flagged context result clears the caller output word while an object-backed result returns the created object's low word - `entity_vm_context_create_from_slot_index` then seeds one `0x6714` context from `entity_vm_slot_load_value_plus_offset`, while the large callers at `000d:208b` and `000d:21ed` continue by reading bytecode-like data from the seeded `+0xd6/+0xd8` lane - The context lane now also has a separate referent-registry subsystem: - `entity_vm_set_field_da_to_global` writes the current referent id to `0x8c94` from context field `+0xda` and then enters the still-misaligned `000c:3350` body - `entity_vm_referent_registry_init` / `entity_vm_referent_registry_destroy` / `entity_vm_referent_registry_alloc` / `entity_vm_referent_registry_release_by_id` / `entity_vm_referent_registry_free_node` show that `0x8c8c/0x8c8e/0x8c90/0x8c94` implement one free-list-backed registry keyed by that current referent id - this is the first solid runtime mechanism showing how referent-only descriptors can still drive script state even when the actual event field lives in a separate neighboring descriptor - the registry now also has a named chain container layer: `entity_vm_referent_chain_copy`, `entity_vm_referent_chain_append_unique_from`, `entity_vm_referent_chain_contains_entry`, `entity_vm_referent_chain_get_entry_data_at`, and `entity_vm_referent_chain_get_indirect_data` show that one referent can own copied/deduplicated payload chains with either inline fixed-size payloads or indirect string-like payloads - That chain layer is now less one-sided than before: - `entity_vm_referent_chain_remove_matching_from` (`000d:6a9a`) removes entries from one chain when they match a second chain, using either inline compare or indirect string compare depending on the chain type byte - `entity_vm_referent_chain_set_entry_data_at` (`000d:6cf6`) updates the payload of the Nth chain entry in place, freeing old indirect payload storage first when needed - `entity_vm_opcode_finish` (`000d:3350`) is now identified as the common opcode epilogue that writes `0x8c94` from the current frame result and unwinds the temporary slot-array state before returning the opcode result - That makes the emerging human-readable script model less ad hoc. A plausible future IR is now: `referent anchor -> payload chain(s) -> event-bearing attachment(s)` rather than a flat list of isolated descriptor rows. - The opcode side now reinforces that IR too: at least one handler family around `000d:0988` can either append unique payload entries or remove matching ones before returning through the same epilogue, which is a better fit for a graph-editing/object-attachment VM than for a pure linear trigger list. - That `000d:0988` family is now classified more tightly at the opcode-id level: - opcode `0x19` = append unique indirect/string-like payload entries into the referent chain - opcode `0x1a` = remove matching indirect/string-like payload entries from the referent chain - opcode `0x1b` = remove matching inline/fixed-size payload entries from the referent chain - the same helper body also implies the missing sibling `0x18` as the inline/fixed-size append-unique form, because only `0x19/0x1a` set the indirect compare flag while only `0x1a/0x1b` take the removal path - The first concrete `000c` to `000d` bridge inside that lane remains `entity_vm_set_value_from_slot_plus_offset` at `000c:f95f`: it calls `entity_vm_slot_load_value_plus_offset`, stores its return pair into object fields `+0xd6/+0xd8`, and sits immediately beside other `entity_vm_*` helpers in the `000c:f6b8..f9d9` mini-VM cluster. On the `000d` side, `entity_vm_slot_load_value_plus_offset` wraps `entity_vm_slot_load_value`, but the old `PUSH 0x410` suspicion at `000d:5290` is now rejected: that site reaches the seg091 fatal-report helper family at `000a:44fd`, not live gameplay dispatch. - The two main `000d` caller blocks beneath that bridge now have a first stable byte/value reading too: - internal block `000d:208b` is the simple materialize-or-forward path: it creates one VM context from the caller's stream state, checks the returned object flags, and either writes the returned value pair straight to the caller output slot or forwards the created object's low word through the shared opcode epilogue - internal block `000d:21ed` is the inline-payload path: it creates the same VM context, prepends the caller-owned blob into the backward-growing context buffer at `+0x102`, then consumes two bytes from the seeded `+0xd6/+0xd8` lane as small shape/count metadata before building an `entity_link` closure matrix from the following caller-stream words and pushing back the non-`0x0400` results - that is the first concrete evidence that the `+0xd6/+0xd8` lane is not only carrying immediate event/value ids; it also carries compact metadata bytes that parameterize larger inline payloads copied from the caller stream - Current JELYHACK implication: because `JELYHACK` and `JELYH2` still expose only `referent`, the most defensible model is now that they provide map/object identity into the referent-registry lane, while one adjacent event-capable record (`REE_BOOT`, `SURCAMEW.eventTrigger`, `SFXTRIG.event`, or another nearby generic `EVENT`/`NPCTRIG`) carries the actual event semantics that can eventually reach `0x410`. - The immediate runtime-owner writer is now pinned down one step further too. `entity_vm_runtime_create` (`000d:4c99`) is the only verified writer of runtime `+0x1315/+0x1317`, and it does so by calling newly recovered `entity_vm_runtime_owner_resource_create` (`000d:7000`). That helper does not simply copy a caller-supplied owner table: it constructs one embedded seg069/070 helper object, queries the needed table size through vtable `+0x04`, allocates child `+0x10/+0x12`, then fills the `0x0d`-stride per-slot producer records through vtable `+0x0c`. The paired release path is `entity_vm_runtime_owner_resource_destroy` (`000d:70fd`). - That narrows the owner/resource classification safely but still stops short of speculative source-format naming. The embedded helper goes through the same seg069/070 object lifecycle used by other file/resource-style helpers (`0009:1c00` init, `0009:1800` destroy), so the most defensible current description is still `runtime owner/resource helper` rather than `USECODE file loader` or a descriptor-specific name. - The first gameplay-side mask families around `entity_vm_context_try_create_masked_for_entity` are also now explicit from instruction evidence: - local wrapper `0004:f033` passes slot mask `0x8000:0007` - `FUN_0004_f05c` passes slot mask `0x2000:0015` and is reached from `0004:f2b3` after overlap/proximity checks plus entity byte `+0x32` state toggling - `FUN_0005_27a4` passes slot mask `0x0001:0000` and is reached from the `000c:a09e` entity `+0x5b` bit-`0x0004` branch - Those masks are enough to prove that the runtime is exposing multiple gameplay-side materialization lanes into the same owner/resource table, but they are not yet enough to tie one lane specifically to the `JELYHACK`/`JELYH2` anchor pair instead of the neighboring event-bearing descriptors (`REE_BOOT`, `SURCAMEW`, `SFXTRIG`, or another local trigger record). - The extractor now emits a first graph-oriented view of that claim too: `referent_anchor_event_graph.tsv` groups referent-bearing rows with nearby event-bearing neighbors, and `jelyhack_island_graph.md` renders the `JELYHACK` / `JELYH2` island as edges to local descriptors. On the current data, the strongest event-bearing neighbors in that island are `REE_BOOT` (`event`), `SURCAMEW` (`eventTrigger`), and `SFXTRIG` (`event`). - The new focused comparison report (`jelyhack_descriptor_compare.tsv`) makes one more structural point explicit: `JELYHACK` and `JELYH2` have identical first 16 header words and the same lone `referent` field tag, while differing only in the label string and one small trailing `wx[...]` literal. That strengthens the reading that they are sibling referent-anchor classes rather than separate event-bearing behavior records. - The same comparison also helps separate anchor classes from event-bearing neighbors: `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` all carry materially richer header/state patterns than `JELYHACK` / `JELYH2`, which is consistent with them holding actual trigger or attachment semantics beside the anchor-only classes. - The `000d:21ed` callee chain is now tighter too. The nested call at `0008:7d27` is `entity_link`, which appends one entity id into another entity's word-list and, unless bit `0x0400` is set, also updates the reciprocal pair-link slots. So the `22bc..2433` opcode block is best understood as building a bidirectional entity-link closure matrix from streamed entity ids, not merely copying opaque words around. - Ghidra now carries that interpretation as a conservative disassembly comment at `000d:22bc`, but not yet as a symbol rename, because the surrounding `000d:208b/21ed/22bc` region is still mis-split into artificial function bodies. - The new `EVENT`-focused reports (`event_island_graph.md`, `event_descriptor_compare.tsv`) broaden the descriptor-side picture beyond the JELYHACK anchor case. The strongest second island is the compact local cluster at indices `186..195`, where `COR_BOOT`, `EVENT`, and `NPCTRIG` all expose explicit `69:0A00 -> event` tags while `ROLL_NS`, `CRUZTRIG`, `NPC_ONLY`, and `VMAIL` stay on the referent/link/text side. - That cluster looks structurally different from JELYHACK in a useful way: `EVENT` is the large hub payload (`0x20AA`) carrying `source`, `dest`, `door`, `link`, `time`, `counter`, `post1`, `post2`, `floor`, and `flicMan`, while `COR_BOOT` and `NPCTRIG` are smaller event-bearing satellites and the surrounding records (`ROLL_NS`, `CRUZTRIG`, `NPC_ONLY`, `VMAIL`) look like attached state/trigger/object descriptors rather than alternate event cores. - The first compare pass on that island is already informative. `COR_BOOT`, `EVENT`, `CRUZTRIG`, `NPC_ONLY`, and `VMAIL` share the same leading `0x00000000` dword class shape, `NPCTRIG` moves to a nearby `0x00000001` shape, and `ROLL_NS` is the obvious outlier with first dword `0x00000002` plus rider/time/cargo fields. So the present best reading is one three-node event-bearing core embedded inside a wider referent-neighbor island, not one flat run of equivalent trigger records. - The extractor now also emits a global event-family pass (`event_family_index.tsv`, `event_family_summary.md`), which turns the local island findings into a wider descriptor taxonomy. Current validated families are: - `event-hub`: `EVENT` - `boot-event-core`: `AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `VAR_BOOT`, `REE_BOOT` - `npc-trigger`: `NPCTRIG` - `minimal-event-core`: `SFXTRIG` - `environmental-event`: `FLAMEBOX`, `NOSTRIL`, `STEAMBOX` - `callback-eventtrigger`: `SURCAMNS`, `SURCAMEW` - That split matters because it is the first extractor-backed distinction between active event carriers and callback-only trigger holders. The `69:0A00 -> event` classes now look like the active event-bearing core of the descriptor system, while the surveillance classes with `24:0A02 -> eventTrigger` are better treated as callback/attachment endpoints rather than peer event hubs. - The extractor now emits a stronger script-facing bridge artifact too: `runtime_descriptor_family_rankings.md` / `.tsv` rank those descriptor families against the verified runtime lanes instead of only listing neighborhoods. Current best fit is `EVENT` as the strongest active-event payload lane, `_BOOT` cores and `NPCTRIG` as strong satellites, `SFXTRIG` / environmental classes as moderate active-event fits, `JELYHACK` / `JELYH2` as the dedicated referent-anchor lane, and `SURCAM*` as structurally distinct callback/attachment holders. - That ranking is anchored by the current owner-loader evidence as well as the descriptor grammar: `000d:44df -> 000d:4c99 -> 000d:7000` supplies the slot-backed source, and raw seg070 windows `0009:67b6` / `0009:6916` now show the embedded helper walking object `+0x10/+0x18` tables, formatting per-entry paths, and open/read/close-loading files before the `0x0d`-stride owner records are materialized. - The next focused pass tightened the `_BOOT` lane too. `boot_family_compare.tsv` now shows that all five `_BOOT` event cores (`AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `VAR_BOOT`, `REE_BOOT`) share the same header skeleton and the same compact field shape (`referent,event,counter,item`). The meaningful differences are payload size and local neighborhood, not descriptor schema. - The new `boot_frontier_graph.md` makes the best early `_BOOT` frontier explicit: `AND_BOOT` and `BRO_BOOT` sit in one compact referent-heavy neighborhood (`OFFWORK`, `GUARD`, `GDOOR_N`, `GDOOR_E`, `BIGCAN`, `CRUMORPH`, `GUARDSQ`, `CARD_NS`, `CARD_EW`, `EWALLEW`/`EWALLNS`) and also point directly at each other as adjacent event-bearing siblings. So the present best reading is a reusable boot-event core template instantiated in several different local object islands, not a set of unrelated boot scripts. - The environmental hazard lane is now similarly constrained. `environmental_family_compare.tsv` shows that `FLAMEBOX` and `STEAMBOX` are close structural siblings with the same active-event backbone (`referent,event,,,direction,count`) and matching `24:0A02 / 24:FC02 / 24:FE02` object-link pattern, while `NOSTRIL` is a smaller fire-specific variant that keeps the active `event` plus dual fire references and count fields but drops the direction/newType side. - Their neighborhoods are different enough to matter: `environmental_event_graph.md` shows `FLAMEBOX` embedded among vent/door/bridge/copy records, `NOSTRIL` among flame/pad/desk/blaster/keypad records, and `STEAMBOX` among bounce/hover/fade/steam/flame box records. So this looks like one hazard-event descriptor family reused across distinct local object islands rather than one single environmental mega-cluster. - The callback lane is tighter too. `callback_trigger_compare.tsv` confirms that `SURCAMNS` and `SURCAMEW` are effectively the same callback-trigger template: identical field set (`referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun`) and identical tag grammar except for the `therma` slot offset (`24:F102` vs `24:F602`). That keeps the `eventTrigger` split credible as a true callback/attachment lane rather than only a spelling variation on active `event` carriers. - The first runtime-side follow-through on those descriptor gains is now a little tighter too. Instruction search around `000d:ebe3` confirms one fixed sequenced VM/opcode driver body, not just a vague constructor helper: it calls `000d:177c`, `000d:1acb`, `000d:0988`, the internal `000d:22bc` link-matrix block, then `000d:1d4a` and `000d:2104` in order. The key negative result is just as useful: `000d:ec31` is only the internal `CALL 000d:22bc` site inside that body, not a standalone function entry. - Ghidra now carries that as a conservative disassembly comment at `000d:ebe3`. That is still short of a safe rename, but it does promote the lane from “suspected constructor chain” to “verified ordered opcode/handler sequence,” which is the clearest current bridge from the descriptor-side event families back into the `000d` VM/object runtime. --- ## Raw 000e RIFF/Animation Cluster The `000e:` segment contains a RIFF/AVI streaming animation subsystem. ### Animation object field map Field offsets relative to the object base pointer: | Offset | Field | |--------|-------| | `+0xb0` | active/valid flag | | `+0xb4`–`+0xc2` | constructor-initialized flags | | `+0xd4` | alive sentinel (must be `-1` for "alive") | | `+0xe4` | paused flag (`0` = running) | | `+0xeaf`/`+0xeb1` | far pointer to current RIFF chunk | | `+0xedb` | animation frame stack depth counter (max 9) | | `+0xee1` | frame data from current chunk `+4` | | `+0xeef` | current subframe index | | `+0x1b3` | subframe count | | `+0xef1` | audio completion flag | | `+0x11b` | ring buffer write pointer | | `+0x11f` | ring buffer read pointer | | `+0x117` | ring buffer base | | `+0x123` | ring buffer end (capacity boundary) | | `+0x102` | resource pointer | | `+0xde` | entry index (multiplied by `0x30` to reach per-entry data at `+0x1c7`) | ### RIFF format notes The game uses standard RIFF/IFF: - LIST magic: `0x5453494c` = `"LIST"` - RIFF magic: `0x46464952` = `"RIFF"` - `"movi"` FourCC subchunk for animation frames - Audio frames tagged `"01wb"` (`0x62773130`) - Video frames handled through a separate path ### Newly renamed functions | Address | Name | Evidence | |---------|------|---------| | `000e:2a28` | `riff_find_chunk_by_type` | Walks RIFF LIST/RIFF chunk list; compares each node's FourCC at `+8` vs `param_2`; returns pointer to matching chunk or NULL | | `000e:2104` | `animation_start` | Finds `"movi"` chunk via `riff_find_chunk_by_type`, inits ring buffer ptrs at `+0x11b` from `+0x117 + duration`, calls `animation_advance_frame`, loops `anim_load_audio_frame` and a second frame-loader thunk path per subframe | | `000e:12f4` | `animation_advance_frame` | Fixed-point `0x1000` timer arithmetic; checks `+0xe4` (paused), advances ring buffer `+0x11b/+0x11f/+0x117/+0x123`; calls advance thunk | | `000e:103f` | `animation_tick` | Guard wrapper: checks `param_1+0xd4 != -1`, then calls `animation_advance_frame(param_1, 0)` | | `000e:06f7` | `anim_load_audio_frame` | Checks chunk tag == `0x62773130` (`"01wb"` = audio stream 1); computes ring buffer free space; copies chunk payload via `0x0000:ffff` thunk; increments subframe index at `+0xeef`; resets at subframe count `+0x1b3` | | `000e:053d` | `anim_load_video_frame_wrapper` | Called once per subframe in `animation_start` immediately after `anim_load_audio_frame`; thin wrapper that forwards to `000e:ffb0` | ### Unresolved callee - `000e:ffb0` remains unresolved (decompiles garbled due to overlapping instructions at `000f:0085/000f:0086`). Current evidence from the `animation_start` loop suggests this path is the video-side subframe loader paired with `anim_load_audio_frame`. ### Constructor pattern All three constructor variants (`000e:2777`, `000e:2860`, `000e:2969`) follow the same layout: 1. Call `FUN_000e_e935` (allocator — produces garbled 11KB decompile, not renamed) 2. Set fields `+0xb4` through `+0xc2` on the result 3. Call `000d:ebe3` directly (confirmed CALL sites at `000e:283e`, `000e:2931`, `000e:29e4`; multi-step chain initializer: calls `177c`, `1acb`, `0988`, `22bc`, `1d4a`, `2104` in sequence) 4. Call `assert_alive_sentinel` (assertion: checks `+0xd4 != -1`) 5. Call `func_0x000eec83` The chain at `000d:ebe3` steps through VM opcode handlers (`000d:177c`, `000d:1acb`, `000d:0988`) that operate on a bytecode VM object with stack pointer at `+0xcc` (decremented by 2 per push) and segment base at `+0xce`. The constructor-side field setup before that sequencer is now slightly tighter too: - variants A and B both set `+0xc0 = 1` before the direct `000d:ebe3` call and derive `+0xc2` from `DS:0x604e` - variant C instead sets `+0xc0 = 0`, `+0xc2 = 1`, and `+0x4c = 0x000d` before the same sequencer call - these direct xrefs make `000d:ebe3` a constructor-side animation sequencer rather than a globally xref-dark dispatcher, but they still do not expose any new wrapper-level opcode number beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988` ### Constructor variant renames | Address | Name | |---------|------| | `000e:223d` | `assert_alive_sentinel` | | `000e:2777` | `animation_ctor_variant_a` | | `000e:2860` | `animation_ctor_variant_b` | | `000e:2969` | `animation_ctor_variant_c` |