- 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.
257 lines
23 KiB
Markdown
257 lines
23 KiB
Markdown
# Raw 000a & 000d: Tracked Handles, Cache Manager & Proximity Buckets
|
||
|
||
Content extracted from `crusader_decompilation_notes.md`. Covers the 000d proximity/visibility bucket cluster, 000a tracked-handle table, generic cache manager, seg082 allocator, seg137/138 palette helpers, and seg004/seg005 startup paths.
|
||
|
||
---
|
||
|
||
## Raw 000d Proximity/Visibility Bucket Cluster
|
||
|
||
Small conservative rename batch from the `000d:cc00-d413` region.
|
||
|
||
| Address | Name | Evidence |
|
||
|---------|------|---------|
|
||
| `000d:cc00` | `entity_compute_proximity_or_visibility_bucket` | Returns bucket `0x40` for null or on-screen entities (`entity_projected_bbox_overlaps_viewport`), else computes a distance bucket from the current reference entity at `0x7e22` with thresholds `0x17d`, `0x281`, `0x3c1` mapping to `0x32`, `0x20`, `0x10`, `0x08` |
|
||
| `000d:d413` | `entity_refresh_recent_proximity_or_visibility_buckets` | Walks the last four active records in the `0x69ac` array, recomputes the same bucket, stores it back to each entry, and calls `000a:6343` when the bucket changes |
|
||
| `000d:cdd0` | `tracked_entity_bucket_prune_invalid_entries` | Walks the `0x69ac` array, validates backing handles through `000a:637a`, and clears entry handles to `0xffff` when the backing object is gone |
|
||
| `000d:cd62` | `tracked_entity_bucket_find_free_main_slot` | Finds the first free entry in the main portion of the `0x69ac` array (`0 .. count-4`) |
|
||
| `000d:cd9a` | `tracked_entity_bucket_find_free_aux_slot` | Finds the first free entry in the auxiliary tail portion of the `0x69ac` array (`count-4 .. count-1`) |
|
||
|
||
### Supporting caller notes
|
||
|
||
- `000d:ce1e` populates one `0x69ac` entry by reserving a free slot, computing the initial bucket through `entity_compute_proximity_or_visibility_bucket`, storing both current and previous bucket fields, then allocating/linking the backing handle through `000a:5f36`.
|
||
- `000d:d409` is a thin wrapper that only calls `entity_refresh_recent_proximity_or_visibility_buckets`.
|
||
- `000d:cfad` is an update-or-allocate helper for `(param_1,param_2)` pairs: it tries to update an existing tracked entry through `000a:606a`, clears dead entries, and falls back to `000d:ce1e` allocation when no live match remains.
|
||
- `000d:cec5` is the auxiliary-slot allocator: it prunes invalid entries, uses `tracked_entity_bucket_find_free_aux_slot`, tags the new entry with byte `+0x0a = 1`, and seeds its handle via `000a:5f36(..., flag=1)`.
|
||
- `000a:606a` = `tracked_entity_bucket_handle_update_or_alloc` — updates the backing handle for an existing tracked bucket entry when possible, or falls back to allocation via `000a:5f36` if the handle has gone stale.
|
||
- `000d:d350` = `tracked_entity_bucket_set_value` — finds a tracked `(entity_id, entity_ref)` entry and pushes a new bucket value into its backing handle through `000a:6343`.
|
||
- `000d:d10b` = `tracked_entity_bucket_clear_ref_field` — clears only the `+0x02` reference field for all matching entries.
|
||
- `000d:d151` = `tracked_entity_bucket_remove_by_ref` — marks matching entries' backing handles for removal and clears the local entry handle/reference fields.
|
||
- `000d:d1b1` = `tracked_entity_bucket_remove_tagged_by_ref` — same removal path, but only for entries whose byte `+0x0a` tag is set.
|
||
|
||
---
|
||
|
||
## Raw 000a Tracked-Handle Table
|
||
|
||
The `0x4673` table is the backing handle registry for the `0x69ac` tracked-entry bucket subsystem. That client layer sits on top of a separate generic cache manager rooted at `0x4688..0x46b7`.
|
||
|
||
| Address | Name | Evidence |
|
||
|---------|------|---------|
|
||
| `000a:5f02` | `tracked_entity_handle_find_slot` | Linear scan over 12 entries in the `0x4673` table for a matching 32-bit handle id |
|
||
| `000a:602b` | `tracked_entity_handle_is_live` | Returns true only when a handle exists in `0x4673` and its flag word at `+0x0a` does not have bit `0x0002` set |
|
||
| `000a:60eb` | `tracked_entity_handle_mark_remove` | Sets bit `0x0002` in the handle-table flag word and dispatches through the unresolved cleanup path |
|
||
| `000a:612e` | `tracked_entity_handle_mark_remove_all` | Iterates all 12 handle-table entries and marks each live handle for removal |
|
||
| `000a:6167` | `tracked_entity_handle_alloc_slot` | Allocates a slot in one of two ranges (`0..7` or `8..11`) depending on the aux flag; when full, wraps in a ring and evicts via `tracked_entity_handle_mark_remove` before reusing the slot |
|
||
| `000a:6228` | `tracked_entity_handle_prune_removed` | Reaps entries previously marked with bit `0x0002`, clears dead slots, and refreshes high-index entries through `000a:6b2d` |
|
||
| `000a:63bc` | `tracked_entity_handle_find_by_entity` | Finds the first live handle-table entry whose key/entity word at `+0x04` matches the requested entity id |
|
||
|
||
### Handle entry layout (stride `0x0c`)
|
||
|
||
| Offset | Field |
|
||
|--------|-------|
|
||
| `+0x00` | 32-bit handle id |
|
||
| `+0x04` | key/entity id |
|
||
| `+0x06` | class/group/source-style selector |
|
||
| `+0x08` | current bucket/value |
|
||
| `+0x0a` | flags (`bit0` = aux-slot allocation, `bit1` = pending removal) |
|
||
|
||
### Thin public wrappers
|
||
|
||
| Address | Name |
|
||
|---------|------|
|
||
| `000a:5276` | `entity_bucket_track_default_main` — gated by `0x45aa`; creates or refreshes a main-slot tracked handle with bucket `0x40` and selector `0xff` |
|
||
| `000a:5294` | `entity_bucket_track_main` — same path, but takes the bucket value as an argument for the main-slot range |
|
||
| `000a:52d0` | `entity_bucket_track_default_aux` — aux-slot variant with default bucket `0x40` |
|
||
| `000a:52ee` | `entity_bucket_track_aux` — aux-slot variant with explicit bucket argument |
|
||
|
||
---
|
||
|
||
## Raw 000a Generic Cache Manager
|
||
|
||
Follow-up analysis of `000a:6b2d` and the `0x4688..0x46b7` globals shows that this region is a generic cache manager used by the tracked-handle layer, not part of the tracked-entity subsystem itself.
|
||
|
||
| Address | Name | Evidence |
|
||
|---------|------|---------|
|
||
| `000a:6b2d` | `cache_lookup_or_load_entry_by_id` | Fast-paths the last id via `0x46af/0x46b1`, otherwise searches `0x469d`, evicts older cache slots until there is room under byte budget `0x46a5`, allocates a block from the free-list, clears/initializes the payload, records the id, and dispatches through the loader interface at `0x468c` |
|
||
| `000a:6a95` | `cache_release_entry_by_slot` | Releases a cached slot by index, clears any client references through `000a:62d8`, frees its backing block through `cache_free_block_by_slot`, and marks the slot id in `0x469d` as unused (`0xffff`) |
|
||
| `000a:6d07` | `cache_alloc_block_for_slot` | Allocates or splits a block from the free-list anchored at `0x4688`, tags it with the owning cache slot index, and updates the in-use byte count at `0x46a9` |
|
||
| `000a:6f4d` | `cache_free_block_by_slot` | Finds the free-list node for a cache slot, marks it free, subtracts its size from `0x46a9`, and coalesces adjacent free blocks |
|
||
| `000a:67d9` | `cache_shutdown` | Tears down the generic cache manager: flushes/reset state, frees slot arrays at `0x4699/0x469d/0x46b3`, frees the free-list container at `0x4688`, and closes backing state at `0x4691` |
|
||
| `000a:6898` | `cache_set_loader_interface` | Installs the backend loader/callback interface pointer at `0x468c` |
|
||
|
||
### Cache globals
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `0x4688` | free-list/block-list head | Used by `cache_alloc_block_for_slot` and `cache_free_block_by_slot` |
|
||
| `0x468c` | cache_loader_interface | Backend callback table; `+0x34` = size query, `+0x0c` = load/bind callback |
|
||
| `0x4695` | arena base pointer | Base for the raw cache payload arena |
|
||
| `0x4699` | per-slot payload-pointer table | |
|
||
| `0x469d` | per-slot cached id table | `0xffff` = unused |
|
||
| `0x46a5` | byte budget / arena capacity | |
|
||
| `0x46a9` | bytes currently in use | |
|
||
| `0x46af/0x46b1` | fast-path cache | Last requested id and slot index |
|
||
| `0x46b3` | per-slot block metadata mirror | Used when releasing or refreshing slots |
|
||
|
||
---
|
||
|
||
## Follow-up: Cache Init and Runtime State
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `000a:6600` | `cache_init` | Stores slot count in `0x46ad`; allocates per-slot payload-pointer table; seeds each slot; queries/derives arena size; allocates arena backing object at `0x4691`; allocates per-slot metadata mirrors; initializes free-list head at `0x4688`; calls `cache_reset_runtime_state` |
|
||
| `000a:68aa` | `cache_reset_runtime_state` | Shared cache reset/bootstrap helper called from `cache_init`, `cache_shutdown`, and external reset paths. Allocates per-slot arena-header nodes, rebinds slot pointers to arena base, clears the cached-id table, seeds the free-list head, and resets `0x46a9` (bytes in use) plus `0x46af` (last-id fast path) |
|
||
| `000a:703e` | `cache_compact_arena_blocks` | Compacts live cache arena blocks into earlier free holes when allocation would fail, updates per-slot payload pointers, and merges adjacent free-list headers afterward |
|
||
|
||
---
|
||
|
||
## Follow-up: Tracked-Handle Table Init/Shutdown
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `000a:5e00` | `tracked_entity_handle_table_init` | If `0x4672` is clear, allocates `0x90` bytes at `0x4673/0x4675`, aborts through `runtime_init_or_abort` on failure, calls `000a:577d` and local helper `000a:5e95`, then sets `0x4672 = 1` |
|
||
| `000a:5e59` | `tracked_entity_handle_table_shutdown` | Matching teardown for `tracked_entity_handle_table_init` |
|
||
| `000a:5e95` | `tracked_entity_handle_table_clear_and_dispatch` | When `tracked_entity_handle_table_active` is set, zeroes the full `0x90`-byte handle table at `0x4673`, resets adjacent local state at `0x4677/0x4679/0x467b`, then dispatches through the remaining thunked follow-up path |
|
||
| `000a:5339` | `tracked_entity_handle_mark_remove_all_if_enabled` | Thin gate wrapper that only forwards to `tracked_entity_handle_mark_remove_all` when `tracked_entity_bucket_system_enabled` is set |
|
||
|
||
Table globals: `0x4672` = `tracked_entity_handle_table_active`, `0x4673/0x4675` = `tracked_entity_handle_table` (12 entries × `0x0c` = `0x90` bytes).
|
||
|
||
---
|
||
|
||
## Follow-up: Tracked Bucket System Init/Shutdown
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `000a:5186` | `tracked_entity_bucket_system_init` | Allocates a rotating buffer via `0009:3600`, lazily creates `tracked_entity_bucket_backend_object` through `0009:5600` when absent, installs that object into `cache_loader_interface`, allocates the tracked handle table via `000a:5e00`, allocates the 32-entry `0x69ac` bucket array via `000d:cca3(0x20)`, then sets `tracked_entity_bucket_system_enabled` |
|
||
| `000a:538e` | `tracked_entity_bucket_system_init_if_configured` | Only calls the init routine when config/feature gate `0x89f4` is non-zero |
|
||
| `000a:5223` | `tracked_entity_bucket_system_shutdown` | Tears down the tracked handle table, frees the `0x69ac` bucket array, calls backend-object vtable slot `+0x38` with `(3, backend_object)`, clears `tracked_entity_bucket_backend_object`; called from the wider engine teardown routine at `0004:621b` |
|
||
|
||
System globals: `0x45aa` = `tracked_entity_bucket_system_enabled`, `0x45ab/0x45ad` = `tracked_entity_bucket_backend_object`.
|
||
|
||
Public thin gate wrappers that feed the `0x69ac` tracked-entry layer:
|
||
- `0005:3b34` = `tracked_entity_bucket_alloc_main_if_enabled`
|
||
- `0005:3b53` = `tracked_entity_bucket_alloc_aux_if_enabled`
|
||
- `0005:3b72` = `tracked_entity_bucket_remove_by_entity_and_ref_if_enabled` → forwards into `000d:d086 = tracked_entity_bucket_remove_by_entity_and_ref` when `0x45aa` is set.
|
||
|
||
---
|
||
|
||
## Follow-up: Backend Object Constructor
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `0009:5600` | `cache_backend_object_init` | Allocates a `0x20`-byte object when caller passes null; initializes embedded DOS file-handle state via `dos_file_handle_init`; seeds internal method-table / state fields at object offsets `+0x08`, `+0x0c`, `+0x10`, `+0x14`, `+0x16`, `+0x18`, and `+0x1c`; dispatches through the object method table during construction; returns the object pointer cached at `0x45ab/0x45ad` |
|
||
|
||
Verified callback roles inside `cache_lookup_or_load_entry_by_id`:
|
||
- backend vtable `+0x34` = size query callback for a cache entry id (used before allocation/eviction)
|
||
- backend vtable `+0x0c` = load/bind callback that populates the newly allocated slot buffer for the requested id
|
||
|
||
---
|
||
|
||
## Follow-up: External Reset Paths
|
||
|
||
- The path around `0004:25a9` classifies as an external reset sequence: it calls `cache_reset_runtime_state`, then `tracked_entity_handle_table_clear_and_dispatch`, then continues through additional tracked-entry/cache-side refresh helpers (`000d:cd22`, `000d:44b3`, `0006:ae66`, `0006:ae00`, etc.).
|
||
- The path around `0004:eb80` is a conditional tracked-bucket reset/update sequence: when `tracked_entity_bucket_system_enabled` is set, it calls `tracked_entity_handle_mark_remove_all_if_enabled`, then `tracked_entity_handle_table_clear_and_dispatch`, then `cache_compact_arena_blocks`, before resuming its outer flow.
|
||
|
||
---
|
||
|
||
## Follow-up: Repaired seg004 Reset-Path Function Objects
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `0004:2592` | `runtime_cache_reset_sequence` | Calls `0008:7bfe`; calls `game_mode_init(*(0x27c4))`; calls import-resolved site `0004:25a4`, now verified from the separately imported `ASYLUM.DLL` as ordinal `24` = `_ASS_StopAllSFX`; then resets cache runtime state, clears tracked handles, refreshes tracked-entry/cache helpers. Known caller: `0004:262d` inside the tiny wrapper at `0004:2620`, which sets byte `+0x40` on the object at `0x6828` before invoking the reset sequence. |
|
||
| `0004:eb1f` | `entity_dispatch_entry_ctor_0f3a_with_cache_reset` | Allocates/initializes an entity dispatch entry; stamps entry type `0x0f3a`; stores its two word payload fields; runs local setup through embedded helper at `0004:ebf4` (which dispatches `entity_dispatch_reset_all(*0x7e22, 0x00f0)` and — when the local flag plus global `0x0ee1` allow it — allocates a type `0x0f5e` dispatch entry and passes it to `entity_pair_sync_b`); when `tracked_entity_bucket_system_enabled` is set, performs the tracked-handle removal / clear / cache-compaction sequence before finalizing through `0009:b1c3` in phase `0`. |
|
||
| `0004:ea00` | `entity_dispatch_entry_alloc_type_0f5e` | Reuses the incoming FAR pointer when non-null; otherwise allocates `0x33` bytes through `mem_alloc_far`; initializes the entry through `entity_dispatch_entry_init`; stamps the entry type word at `+0x00` to `0x0f5e`. |
|
||
|
||
---
|
||
|
||
## Follow-up: seg082 Allocator Cluster
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `0009:a229` | *(size-only wrapper)* | Public size-only wrapper around the seg082 allocator. Lazily initializes the allocator on first use through `0009:bcb9`, then calls `allocator_try_alloc_from_head_table(size, default_tag, 0xff)`. |
|
||
| `0009:bcb9` | *(lazy initializer)* | One-time lazy initializer. Parses an optional `-x` tuning value from the PSP command line, clamps derived percentage into `0x14..0x50`, seeds local seg082 helpers, then sets init flag `0x4096 = 1`. |
|
||
| `0009:b06b` | `allocator_try_alloc_from_head_table` | Validates requested size, reserves a temporary work token through `0009:e15f`, scans the `0x8724` allocator head table in `0x0c`-byte entries via `allocator_head_try_alloc_block`. On success, commits the result through `0009:e2b4`, clears failure flag `0x4098`. When a pass does not find a fit, interleaves up to two finalize phases through `allocator_phase_finalize_pass(phase)` before the final retry. |
|
||
| `0009:a336` | `allocator_head_try_alloc_block` | Per-head first-fit allocator. Normalizes requested size (rounds odd small requests up, page-aligns large non-page-aligned requests), adds `0x0a` node header overhead, enforces minimum of `0x10` bytes. Walks the node chain for one allocator head until it finds a free span large enough. On success, unlinks the chosen free node, either consumes it whole or splits off a remainder when `>= 0x10` bytes remain. On failure, returns `0`. |
|
||
| `0009:a5d1` | `allocator_head_free_block` | Per-head free paired with `allocator_head_try_alloc_block`. Rebuilds the node header from a payload pointer (`payload - 0x0a`), validates the owner/tag word, reinserts the block into one allocator head, and coalesces with adjacent free neighbors when possible. |
|
||
| `0009:b224` | `allocator_free_block_by_ptr` | Converts the payload pointer back through local header helpers, scans the `0x8724` head table for the owning range, dispatches to `allocator_head_free_block`, and aborts if no owning head is found. Known wrappers `0009:a24f` and `0009:a27a` are small checked entry points into this path. |
|
||
| `0009:b1c3` | `allocator_phase_finalize_pass` | Accepts phase bytes `0` or `1`. Forwards that byte twice to the object rooted at `0x4588` through vtable slot `+0x08`. Then sweeps the allocator head table at `0x8724` up to the active head count at `0x879c`, calling `allocator_head_finalize_sweep` on each entry. |
|
||
| `0009:af87` | *(free-space probe)* | Walks the node chain rooted at `0x8724`. For each node, accumulates `node_size - 9` into a running total and tracks the largest single free block. Used by `cache_init` and seg013 path at `0004:833b`. |
|
||
| `0009:a961` | *(per-head finalize sweep)* | Walks one `0x8724` head's node chain, skips odd-tagged spans, coalesces or rewrites eligible spans, and updates head/back-pointer links when deferred space needs to be merged back into the chain. |
|
||
|
||
**Allocator head table structure:**
|
||
- `0x8724` = array of `0x0c`-byte allocator heads
|
||
- `0x879c` = active head count / table limit
|
||
- Per-node size/value encoding manipulated through `0009:c628` and `0009:c6ae`, which read/write a packed 32-bit quantity split across `word + byte + byte` fields
|
||
|
||
---
|
||
|
||
## Follow-up: seg137 Palette and Dispatch-Entry Helper Family
|
||
|
||
A coherent palette-write and palette-backed dispatch-entry emission family tied to the same runtime-state constructor lane.
|
||
|
||
| Address | Name | Evidence |
|
||
|---------|------|---------|
|
||
| `000d:85da` | `vga_palette_set_all_black` | Allocates a `0x100`-entry palette buffer filled with zero RGB triplets, writes it to VGA, frees the scratch buffer. (Previously mis-named `map_object_set_dirty_flag`.) |
|
||
| `000d:8653` | `vga_palette_set_all_white` | Same shape as black — all three RGB components initialized to `0x3f`, then written through `vga_palette_write`. |
|
||
| `000d:86cc` | `vga_palette_set_all_rgb` | Takes caller-supplied RGB bytes, replicates them across a `0x100`-entry palette buffer, writes the result to VGA, frees the scratch palette. |
|
||
| `000d:82ea` | `dispatch_entry_create_black_palette_state_active` | Builds a runtime-state dispatch entry of type `0x051e` from a black `0x100`-entry palette buffer; first sets `g_active_dispatch_entry_farptr[+0x40] = 1`. |
|
||
| `000d:8a47` | `dispatch_entry_create_black_palette_state` | Same as above without marking the active dispatch entry. |
|
||
| `000d:83be` | `dispatch_entry_create_grayscale_palette_state_active` | Reads the current VGA palette, normalizes each triplet by copying the first channel across all three RGB bytes, then builds a runtime-state dispatch entry from that grayscale palette while marking the active dispatch entry. |
|
||
| `000d:875d` | `dispatch_entry_create_solid_palette_state_active` | Validates `0..0x3f` RGB inputs, fills a scratch `0x100`-entry palette buffer with that solid color, builds the same `0x051e` runtime-state dispatch entry, marks the active entry. |
|
||
| `000d:88b2` | `dispatch_entry_create_solid_palette_state` | Same as above without marking the active dispatch entry. |
|
||
|
||
Additional caller-side comments (not renamed) added on:
|
||
- `000d:84f4` — current-palette dispatch entry paired with a second object of type `0x68bf` through `entity_pair_sync_b`
|
||
- `000d:89c6` — parameterized current-palette runtime-state wrapper with active-state flags
|
||
|
||
---
|
||
|
||
## Follow-up: seg138 Caller-Side Dispatch-Entry Emission Helper
|
||
|
||
`FUN_000d_938c` (`000d:938c-000d:9583`) — a real caller-side helper with an evidence-preserving decompiler comment added in Ghidra instead of forcing a speculative rename.
|
||
|
||
Current verified behavior:
|
||
- When the mode/global gate is not already in the `0x13:0x0008` state and entity byte `+0x33` is clear, it allocates a scratch palette buffer, constructs a dispatch entry, sets type `0x051e`, and initializes runtime state through `entity_dispatch_entry_init_runtime_state` with entry kind `0x3c`.
|
||
- Later in the same helper it constructs a second dispatch entry from the current palette globals at `0x4e4:0x4e6`, again sets type `0x051e`, and initializes runtime state with entry kind `0x14` and active-state parameters `(1,0,1)`.
|
||
- Both created entries are polled until their runtime flag word clears bit `0x0002`, after which the helper redraws the global sprite path, syncs display-state byte `0x58e` from the entity when the global display object exists, calls `FUN_0006_16e1`, clears `g_active_dispatch_entry_farptr[+0x40]`, and finally dispatches through the input object's vtable slot `+0x08`.
|
||
|
||
---
|
||
|
||
## Follow-up: seg005 Startup/Display Orchestration
|
||
|
||
| Address | Name | Notes |
|
||
|---------|------|-------|
|
||
| `0004:60c0` | `FUN_0004_60c0` | Startup/display orchestration path: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, runs the seg137 palette and dispatch-entry helper family, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into the still-unrecovered `0004:1e00` routine. |
|
||
| `000d:7600` | `active_dispatch_entry_mark_enabled` | Marks the active dispatch entry enabled |
|
||
| `000d:760e` | `active_dispatch_entry_mark_disabled` | Marks the active dispatch entry disabled |
|
||
| `000d:761c` | `active_dispatch_entry_create_default` | Creates the default active dispatch entry |
|
||
|
||
---
|
||
|
||
## Follow-up: `0x4588` Object-Role Evidence
|
||
|
||
The `0x4588` FAR object is a runtime-installed callback/dispatch object that participates in conditional render or presentation-side flow. It has an explicit install, clear, callback, and teardown lifecycle.
|
||
|
||
### Verified lifecycle
|
||
|
||
- **Install:** `000a:4932` and `000a:4936` store the incoming dword into `0x4590` and `0x458c`, then `000a:493e` stores the incoming FAR object pointer into `0x4588`.
|
||
- **Clear:** `0004:5b8c` and `0004:5bbf` both clear `0x4588` immediately before the fatal/reporting-style seg091 call through `000a:454d`; `0004:5ea7` and `0004:6430` both clear `0x4588` and then immediately run the one-shot teardown path `000a:4a56(1)`.
|
||
- **Teardown:** `000a:4a56` checks a once-flag at `0x4595`, clears `0x4588` when non-null, optionally performs a vtable `+0x0c` callback when `0x4590 != 0x458c`, then calls vtable slot `+0x04` followed by `FUN_0009_0d30()`.
|
||
- **Callbacks:** `000a:b9e5`, `000a:ba66`, `000d:9d5e`, and `000d:a3b7` all push a two-word value pair followed by the `0x4588` FAR pointer and call vtable slot `+0x0c`. `entity_conditional_render_dispatch` calls the same vtable slot with a single literal `0x0101` argument.
|
||
|
||
### Payload pairs from payload sync callsites
|
||
|
||
- `000d:9d5e` → vtable `+0x0c` payload from object fields `+0x12d/+0x12f`
|
||
- `000d:a3b7` → vtable `+0x0c` payload from object fields `+0x74f/+0x751`
|
||
- `000a:b9e5`, `000a:ba66` → emitting only when the candidate two-word pair differs from the current pair, then mirroring that pair through `000b:1e39` using global sprite/object pointer `0x4f38/0x4f3a`
|
||
|
||
### Globals
|
||
|
||
| Address | Name |
|
||
|---------|------|
|
||
| `0x4588` | runtime FAR object pointer (nullable) |
|
||
| `0x458c` | callback sync field (compared against `0x4590` in teardown) |
|
||
| `0x4590` | paired sync field |
|
||
| `0x4594/0x4595` | state flags |
|
||
| `0x45a6` | clock/cookie global used by `assert_buffer_valid` |
|
||
| `0x39ca` | dispatch callback-table pointer |
|
||
| `0x6828` | `g_active_dispatch_entry_farptr` |
|