Crusader_Decomp/docs/presentation-callback-broker-layout.md
2026-04-09 00:32:12 +02:00

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 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.