8.4 KiB
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
0x458cand0x4590 - once guards at
0x4594and0x4595 - 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_once000a: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
0x458cand0x4590 - 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
0x4588when an object is present - emits vtable
+0x0ccallback only when0x4590 != 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
0or1 - forwards that phase twice through broker vtable slot
+0x08 - then sweeps allocator heads through
allocator_head_finalize_sweep
Current safest role:
- slot
+0x08looks 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:
releaseorshutdown
Slot +0x08
Evidence:
allocator_phase_finalize_passforwards phase0or1twice through this slot
Current working meaning:
phase_finalizeoradvance_finalize_phase
Slot +0x0c
Evidence:
- teardown emits it conditionally when recorded state changed
entity_cleanup_resources_and_dispatchuses it at000d:9d5eand000d:a3b7- raw notes also document callers
000a:b9e5and000a:ba66 - one caller uses literal mode-like pair
0x0101
Current working meaning:
emit_state_pairordispatch_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_dispatchcall at000d:9d5euses object fields+0x12d/+0x12f- matching call at
000d:a3b7uses object fields+0x74f/+0x751 - one live caller uses literal
0x0101
What this supports now:
- slot
+0x0cis 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:
- create a conservative owner like
PresentationCallbackBroker - model the global state cluster as supporting globals, not as instance fields until constructor ownership is tighter
- create a provisional vtable with slots
+0x04,+0x08, and+0x0c - leave slot names conservative and role-based
- 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, orEntityVmOwnerResource
Best Next Evidence To Collect Later
- close more callers of vtable slot
+0x0c - classify the exact payload meaning of the two-word pairs
- decide whether
0x45a6is an owned buffer, fallback object, or adapter scratch lane - 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::PresentationCallbackBrokerin the activeCRUSADER.EXEdatabase. - Re-anchored the global install/teardown pair into live
12d0:helpers by direct access to the0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6broker globals:12d0:0513->InitOnce12d0:0656->TeardownOnce
- Added short decompiler comments to both methods so the current
0x4588lifecycle remains visible in-session:InitOncenow records the0x4594guard, state snapshot into0x458c/0x4590, broker install at0x4588, and fallback-buffer allocation at0x45a6.TeardownOncenow records the0x4595guard, broker clear, conditional slot+0x0cemit when0x4590 != 0x458c, slot+0x04release, and final cleanup path.
- Re-anchored the finalize-phase caller as well:
1288:0fc3is nowallocator_phase_finalize_passin the live database with a comment recording that it exercises broker slot+0x08twice before sweeping allocator heads. - Preserved two verified live slot
+0x0ccallers with decompiler comments instead of forcing premature renames:1278:0616 Vport_1278_0616uses the installed broker callback when the local vport path takes its fallback branch withparam_2 == 0.1320:1588 Dispatch_ModalGumpemits 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
thistyping yet for the broker pointer parameter onInitOnce - no live promotion yet of
allocator_phase_finalize_passunder 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
PresentationCallbackBrokerplaceholder
- no forced
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.