# Presentation Callback Broker Layout ## Purpose This note isolates the current class-lift-relevant evidence for the callback/broker object rooted at `0x4588`. The subsystem name is still intentionally conservative. The goal here is to freeze the object model, lifecycle, and vtable-slot surface that are already well evidenced, so later Ghidra class work does not have to rediscover them. Current working family name: - `PresentationCallbackBroker` That name is still provisional, but it fits the current evidence better than a generic allocator or generic render-object label. ## Why This Looks Like A Real Object Family Current evidence does not support treating `0x4588` as a stray callback function pointer. What is already stable: - one installed nullable FAR object pointer at `0x4588` - explicit once-only install and teardown helpers - live vtable slots `+0x04`, `+0x08`, and `+0x0c` - state snapshot globals at `0x458c` and `0x4590` - once guards at `0x4594` and `0x4595` - a fallback/auxiliary buffer pointer at `0x45a6` That is strong enough to treat this as a typed broker/helper object with global lifetime rather than as a raw callback cell. ## Core Lifecycle ### Install Strong anchors: - `000a:4913 runtime_callback_object_init_once` - `000a:4a1f video_bios_state_snapshot` Current verified behavior from the raw notes: - checks one-time guard `0x4594` - snapshots state through `video_bios_state_snapshot` - stores previous/current state words in `0x458c` and `0x4590` - installs the incoming FAR object pointer at `0x4588` - ensures fallback buffer allocation at `0x45a6` Current safest role: - object installation is tied to video or presentation state capture, not to generic allocation alone ### Teardown Strong anchor: - `000a:4a56 runtime_callback_object_teardown_once` Current verified behavior: - checks once-only teardown guard `0x4595` - clears `0x4588` when an object is present - emits vtable `+0x0c` callback only when `0x4590 != 0x458c` - then calls vtable slot `+0x04` - follows with cleanup through `FUN_0009_0d30()` Current safest role: - the broker is not only a notifier; it also owns a release/finalize path and a final conditional dispatch when presentation state changed ### Finalize sweep phase Strong anchor: - `0009:b1c3 allocator_phase_finalize_pass` Verified behavior: - accepts finalize phase `0` or `1` - forwards that phase twice through broker vtable slot `+0x08` - then sweeps allocator heads through `allocator_head_finalize_sweep` Current safest role: - slot `+0x08` looks like a finalize or phase-advance callback surface shared with allocator/presentation cleanup, not a normal per-entity method ## Vtable Surface ### Slot `+0x04` Evidence: - teardown path calls it as the broker release path Current working meaning: - `release` or `shutdown` ### Slot `+0x08` Evidence: - `allocator_phase_finalize_pass` forwards phase `0` or `1` twice through this slot Current working meaning: - `phase_finalize` or `advance_finalize_phase` ### Slot `+0x0c` Evidence: - teardown emits it conditionally when recorded state changed - `entity_cleanup_resources_and_dispatch` uses it at `000d:9d5e` and `000d:a3b7` - raw notes also document callers `000a:b9e5` and `000a:ba66` - one caller uses literal mode-like pair `0x0101` Current working meaning: - `emit_state_pair` or `dispatch_state_change` Current caution: - payload semantics are still not closed well enough to call this a specific display-mode or palette-event method with confidence ## Global State Cluster | Address | Current role | |---------|--------------| | `0x4588` | nullable FAR broker object pointer | | `0x458c` | recorded state word A | | `0x4590` | recorded state word B | | `0x4594` | install-once guard | | `0x4595` | teardown-once guard | | `0x45a6` | fallback or auxiliary FAR buffer | Current safest class-lift read: - this global cluster behaves like one installed process-wide broker instance plus its remembered state and support buffer ## Caller-Side Payload Evidence The payload side is important even though exact semantics are still open. Verified pairs already documented: - `entity_cleanup_resources_and_dispatch` call at `000d:9d5e` uses object fields `+0x12d/+0x12f` - matching call at `000d:a3b7` uses object fields `+0x74f/+0x751` - one live caller uses literal `0x0101` What this supports now: - slot `+0x0c` is consuming compact two-word state/payload pairs - those pairs are presentation-adjacent enough to align with the earlier video-state snapshot evidence What it does not yet support: - one final semantic label for those two words across every callsite ## Relationship To Other Families ### Presentation and startup/display lane The broker note belongs next to the startup/display and runtime-state family notes because its callers overlap with that cleanup/palette/presentation lane. Strong surrounding anchors: - `entity_cleanup_resources_and_dispatch` - palette/state handoff work in `FUN_000d_938c` - active dispatch-entry hold token at `g_active_dispatch_entry_farptr[+0x40]` Current safest read: - the broker is presentation-side infrastructure shared by cleanup/finalize paths, not a child class of `EntityDispatchEntry` ### Allocator lane The allocator interacts with this broker through finalize phases, but current evidence still reads as `allocator client callback` rather than `allocator-owned object`. That means: - keep allocator classes and this broker separate in future class modeling ## Class-Lift Guidance If promoted in Ghidra later, safest current move is: 1. create a conservative owner like `PresentationCallbackBroker` 2. model the global state cluster as supporting globals, not as instance fields until constructor ownership is tighter 3. create a provisional vtable with slots `+0x04`, `+0x08`, and `+0x0c` 4. leave slot names conservative and role-based 5. do not promote this family as the first MCP/class-lift pilot Why not first: - lifecycle is strong, but subsystem semantics are still weaker than `EntityDispatchEntry`, `SpriteNode`, or `EntityVmOwnerResource` ## Best Next Evidence To Collect Later 1. close more callers of vtable slot `+0x0c` 2. classify the exact payload meaning of the two-word pairs 3. decide whether `0x45a6` is an owned buffer, fallback object, or adapter scratch lane 4. tighten the constructor/installation provenance in the live NE program, not only the raw notes ## Live Ghidra Authoring Status Verified first live `PresentationCallbackBroker` batch landed on 2026-04-08. - Created class owner `Remorse::PresentationCallbackBroker` in the active `CRUSADER.EXE` database. - Re-anchored the global install/teardown pair into live `12d0:` helpers by direct access to the `0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6` broker globals: - `12d0:0513` -> `InitOnce` - `12d0:0656` -> `TeardownOnce` - Added short decompiler comments to both methods so the current `0x4588` lifecycle remains visible in-session: - `InitOnce` now records the `0x4594` guard, state snapshot into `0x458c/0x4590`, broker install at `0x4588`, and fallback-buffer allocation at `0x45a6`. - `TeardownOnce` now records the `0x4595` guard, broker clear, conditional slot `+0x0c` emit when `0x4590 != 0x458c`, slot `+0x04` release, and final cleanup path. - Re-anchored the finalize-phase caller as well: `1288:0fc3` is now `allocator_phase_finalize_pass` in the live database with a comment recording that it exercises broker slot `+0x08` twice before sweeping allocator heads. - Preserved two verified live slot `+0x0c` callers with decompiler comments instead of forcing premature renames: - `1278:0616 Vport_1278_0616` uses the installed broker callback when the local vport path takes its fallback branch with `param_2 == 0`. - `1320:1588 Dispatch_ModalGump` emits the broker callback before and after modal dispatch when the requested state pair differs from the current mode snapshot. - This remains intentionally conservative live authoring: - no forced `this` typing yet for the broker pointer parameter on `InitOnce` - no live promotion yet of `allocator_phase_finalize_pass` under the class owner itself, because it is a broker caller rather than a broker method - no attempt yet to rename the wider subsystem beyond the existing `PresentationCallbackBroker` placeholder ## Bottom Line The `0x4588` family is now documented well enough to be treated as a real object candidate. The safest current model is: one global presentation-state callback broker with a small live vtable surface, explicit install/teardown, conditional state-pair emission, and allocator-linked finalize phases.