Crusader_Decomp/docs/presentation-callback-broker-layout.md

227 lines
8.4 KiB
Markdown
Raw Permalink Normal View History

2026-04-05 18:27:09 +02:00
# 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
2026-04-09 00:32:12 +02:00
## 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
2026-04-05 18:27:09 +02:00
## 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.