Crusader_Decomp/docs/presentation-callback-broker-layout.md
2026-04-05 18:27:09 +02:00

6.6 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

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.