Crusader_Decomp/docs/raw-000a-000d.md
MaddoScientisto 3daffbf113 Add extractor for Crusader's EUSECODE.FLX container
- 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.
2026-03-22 14:27:38 +01:00

257 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` |