Enhance segment coverage ledger and mid-project plan with detailed updates

- Added new binary files for segment coverage in `Crusader.rep/idata/00/~00000006.db/`
- Updated `crusader_segment_coverage_ledger.csv` to reflect new findings and classifications, including:
  - Renamed segments for clarity on allocator mechanics and dispatch entry roles.
  - Adjusted coverage status for segments related to startup/display orchestration and allocator phase finalization.
- Revised `plan-mid.md` to include recent progress on segment recovery and classification, emphasizing the ongoing work on the `0x4588` callback object and related functions.
This commit is contained in:
MaddoScientisto 2026-03-21 20:32:21 +01:00
commit d1222a2a4f
7 changed files with 123 additions and 34 deletions

View file

@ -1878,18 +1878,18 @@ Current structural read of the cache globals:
- Reuses the incoming FAR pointer when non-null; otherwise allocates `0x33` bytes through `mem_alloc_far`.
- Initializes the entry through `entity_dispatch_entry_init`.
- Stamps the entry type word at `+0x00` to `0x0f5e` before returning it.
- `0009:b1c3` remains conservatively unnamed, but it is now annotated as a phase-selected finalize helper:
- `0009:b1c3` is now renamed `allocator_phase_finalize_pass` and remains intentionally allocator-scoped rather than subsystem-specific:
- Both known call sites pass only phase bytes `0` or `1`.
- It forwards that byte twice to the object rooted at `0x4588` through vtable slot `+0x08`.
- It then sweeps the table rooted at `0x8724` up to count `0x879c`, calling `FUN_0009_a961` on each entry.
- That evidence is strong enough for comments, but not yet enough to promote `0009:b1c3` or `0x4588` to a more specific subsystem name.
- It then sweeps the allocator head table at `0x8724` up to the active head count at `0x879c`, calling `allocator_head_finalize_sweep` on each entry.
- That evidence is strong enough for the allocator-side rename, but not yet enough to promote `0x4588` to a more specific subsystem name.
### Follow-up: seg082 allocator cluster (`0009:a229`, `0009:af87`, `0009:b06b`, `0009:b1c3`)
- `0009:a229` is now verified as the public size-only wrapper around the seg082 allocator path.
- Caller evidence:
- `saveslot_table_clear` requests `0x2800` bytes through `0009:a229`, stores the returned FAR pointer at `0x2ba3/0x2ba5`, then zeroes the result in `0x400`-byte chunks.
- The wrapper lazily initializes the allocator on first use through `0009:bcb9`, then calls `0009:b06b(size, default_tag, 0xff)`.
- The wrapper lazily initializes the allocator on first use through `0009:bcb9`, then calls `allocator_try_alloc_from_head_table(size, default_tag, 0xff)`.
- `0009:bcb9` is now annotated as the one-time lazy initializer for this path.
- It parses an optional `-x` tuning value from the PSP command line, clamps the derived percentage into `0x14..0x50`, then seeds local seg082 helpers before setting init flag `0x4096 = 1`.
- Table structure around `0x8724` is tighter now:
@ -1900,24 +1900,24 @@ Current structural read of the cache globals:
- It walks the node chain rooted at `0x8724`.
- For each node, it accumulates `node_size - 9` into a running total and tracks the largest single free block.
- Known callers include `cache_init` and the seg013 path at `0004:833b`, both of which use it to size subsequent allocation work.
- `0009:b06b` has been traced further as the internal sweep allocator for this cluster.
- It validates the requested size, reserves a temporary work token through `0009:e15f`, and scans the `0x8724` table in `0x0c`-byte entries via the local helper at `0009:a336`.
- `0009:b06b` is now renamed `allocator_try_alloc_from_head_table`.
- It validates the requested size, reserves a temporary work token through `0009:e15f`, and scans the `0x8724` allocator head table in `0x0c`-byte entries via the local helper at `0009:a336`.
- On a successful fit, it commits the result through `0009:e2b4`, clears failure flag `0x4098`, and returns the allocated FAR pointer.
- When a pass does not find a fit, it interleaves up to two finalize phases through `0009:b1c3(phase)` before the final retry, then releases the work token through `0009:e1f6`.
- When a pass does not find a fit, it interleaves up to two finalize phases through `allocator_phase_finalize_pass(phase)` before the final retry, then releases the work token through `0009:e1f6`.
- The formerly missing callee at `0009:a336` has now been recovered in-place as `allocator_head_try_alloc_block` with body `0009:a336-0009:a5d0`.
- It is the per-head first-fit allocator helper used by `0009:b06b` while sweeping the `0x8724` head table.
- It is the per-head first-fit allocator helper used by `allocator_try_alloc_from_head_table` while sweeping the `0x8724` allocator head table.
- It normalizes the requested size (rounds odd small requests up, page-aligns large non-page-aligned requests), adds the local `0x0a` node header overhead, and enforces a minimum allocation size of `0x10` bytes.
- It walks the node chain for one allocator head until it finds a free span large enough.
- On success it unlinks the chosen free node, either consumes it whole or splits off a remainder node when at least `0x10` bytes remain, stores the owner/tag word, and returns `payload_ptr + 0x0a`.
- On failure for that head it returns `0`, which matches the calling pattern in `0009:b06b`.
- On failure for that head it returns `0`, which matches the calling pattern in `allocator_try_alloc_from_head_table`.
- Boundary follow-up from the same read-only scan:
- The adjacent missing body at `0009:a5d1` has now also been recovered in-place as `allocator_head_free_block` with body `0009:a5d1-0009:a960`.
- It is the per-head free helper paired with `allocator_head_try_alloc_block`.
- It rebuilds the node header from a payload pointer (`payload - 0x0a`), validates the owner/tag word against the expected caller-supplied tag, reinserts the block into one allocator head, and coalesces with adjacent free neighbors when possible.
- Its earlier branch targets `0009:a7a1` and `0009:a7b8` are now confirmed to be internal labels, not separate function entries.
- `0009:a961` is now better constrained as the per-head finalize sweep used by `0009:b1c3`.
- `0009:a961` is now better constrained as the per-head finalize sweep used by `allocator_phase_finalize_pass`.
- It walks one `0x8724` head's node chain, skips odd-tagged spans, coalesces or rewrites eligible spans, and updates head/back-pointer links when deferred space needs to be merged back into the chain.
- This strengthens the current interpretation that `0009:b1c3` is a phase-selected allocator-finalize pass rather than a cache-specific public API.
- This strengthens the current interpretation that `allocator_phase_finalize_pass` is allocator-side callback/finalize glue rather than a cache-specific public API.
- `0009:b224` is now named `allocator_free_block_by_ptr`.
- Current verified behavior: converts the payload pointer back through the local header helpers, scans the `0x8724` head table for the owning range, dispatches to `allocator_head_free_block`, and aborts if no owning head is found.
- Known wrappers `0009:a24f` and `0009:a27a` are now clearly small checked entry points into this free-by-pointer path.
@ -1925,8 +1925,8 @@ Current structural read of the cache globals:
- `allocator_head_try_alloc_block` (`0009:a336`)
- `allocator_head_free_block` (`0009:a5d1`)
- `allocator_free_block_by_ptr` (`0009:b224`)
- `0009:b1c3` now has a corrected single-byte phase parameter in Ghidra; the object at `0x4588` is still left unnamed because its subsystem role is not yet strong enough.
- This narrows the remaining ambiguity around `0009:b1c3`: the unresolved part is now the role of the object at `0x4588`, not the local allocator mechanics around `0x8724`. `ASYLUM.24` is still not identified from the current evidence.
- `allocator_phase_finalize_pass` now has a corrected single-byte phase parameter in Ghidra; the object at `0x4588` is still left unnamed because its subsystem role is not yet strong enough.
- This narrows the remaining ambiguity around `allocator_phase_finalize_pass`: the unresolved part is now the role of the object at `0x4588`, not the local allocator mechanics around `0x8724`. `ASYLUM.24` is still not identified from the current evidence.
### Follow-up: `0x4588` object-role evidence (Priority 1 start)
@ -1934,6 +1934,7 @@ Current structural read of the cache globals:
- Current verified behavior from those uses:
- `entity_conditional_render_dispatch` (`0009:9216`) calls through the runtime-installed object at `0x4588` via vtable slot `+0x0c` when the entity flags allow the alternate path and `param_2 == 0`.
- `000a:4a56` is a one-shot teardown/reset path for the same object: it checks a local once-flag at `0x4595`, clears `0x4588` when non-null, optionally performs a vtable `+0x0c` callback when `0x4590 != 0x458c`, then calls vtable slot `+0x04` followed by `FUN_0009_0d30()`.
- The two callback sync sites inside `sprite_node_get_or_traverse` (`000a:b9e5` and `000a:ba66`) only emit vtable `+0x0c` when the candidate two-word pair differs from the current pair, then immediately mirror that pair through `000b:1e39` using global sprite/object pointer `0x4f38/0x4f3a`.
- A read-only data probe of `0x4588` in the current database returned all zero bytes, so the object pointer is null-initialized statically and likely installed later at runtime.
- Conservative conclusion:
- The `0x4588` object now looks like a runtime-installed callback / dispatch object that participates in conditional render or presentation-side flow and has an explicit teardown path.
@ -1953,6 +1954,88 @@ Current structural read of the cache globals:
- The unresolved part is now its concrete subsystem identity, not whether the object lifecycle is real.
- The best next cheap win is no longer broad instruction searching; it is caller-side recovery around the still-unbounded `000a:b9e5` / `000a:ba66` and `000d:9d5e` / `000d:a3b7` windows.
### Follow-up: seg138 caller-side dispatch-entry emission helper
- `FUN_000d_938c` is now confirmed as a real caller-side helper with body `000d:938c-000d:9583`, and an evidence-preserving decompiler comment was added in Ghidra instead of forcing a speculative rename.
- Current verified behavior from direct MCP decompile/disassembly:
- When the mode/global gate is not already in the `0x13:0x0008` state and entity byte `+0x33` is clear, it allocates a scratch palette buffer, constructs a dispatch entry, sets type `0x051e`, and initializes runtime state through `entity_dispatch_entry_init_runtime_state` with entry kind `0x3c`.
- Later in the same helper it constructs a second dispatch entry from the current palette globals at `0x4e4:0x4e6`, again sets type `0x051e`, and initializes runtime state with entry kind `0x14` and active-state parameters `(1,0,1)`.
- Both created entries are polled until their runtime flag word clears bit `0x0002`, after which the helper redraws the global sprite path, syncs display-state byte `0x58e` from the entity when the global display object exists, calls `FUN_0006_16e1`, clears `g_active_dispatch_entry_farptr[+0x40]`, and finally dispatches through the input object's vtable slot `+0x08`.
- Conservative conclusion:
- seg138 now has one more verified caller tying `entity_dispatch_entry_init_runtime_state` to palette/presentation-side emission work around entity cleanup and redraw flow.
- This narrows the open question from "is the dispatch-entry lane real?" to "what exact presentation/event subsystem does this lane belong to?"
### Follow-up: seg137 palette and dispatch-entry helper family
- A larger direct MCP rename batch now stabilizes a coherent seg137 palette helper family:
- `000d:82ea` = `dispatch_entry_create_black_palette_state_active`
- `000d:83be` = `dispatch_entry_create_grayscale_palette_state_active`
- `000d:85da` = `vga_palette_set_all_black`
- `000d:8653` = `vga_palette_set_all_white`
- `000d:86cc` = `vga_palette_set_all_rgb`
- `000d:875d` = `dispatch_entry_create_solid_palette_state_active`
- `000d:88b2` = `dispatch_entry_create_solid_palette_state`
- `000d:8a47` = `dispatch_entry_create_black_palette_state`
- Current verified behavior from direct MCP decompile/disassembly:
- `vga_palette_set_all_black` corrects the earlier overreach rename at `000d:85da`: it allocates a `0x100`-entry palette buffer filled with zero RGB triplets, writes it to VGA, and frees the scratch buffer. The previous `map_object_set_dirty_flag` name was not supported by the recovered body.
- `vga_palette_set_all_white` is the same helper shape with all three RGB components initialized to `0x3f`, then written through `vga_palette_write`.
- `vga_palette_set_all_rgb` takes caller-supplied RGB bytes, replicates them across a `0x100`-entry palette buffer, writes the result to VGA, and frees the scratch palette.
- `dispatch_entry_create_black_palette_state_active` and `dispatch_entry_create_black_palette_state` both build a runtime-state dispatch entry of type `0x051e` from a black `0x100`-entry palette buffer; the `_active` form first sets `g_active_dispatch_entry_farptr[+0x40] = 1`, while the quiet form does not.
- `dispatch_entry_create_grayscale_palette_state_active` reads the current VGA palette, normalizes each triplet by copying the first channel across all three RGB bytes, then builds a runtime-state dispatch entry from that grayscale palette while marking the active dispatch entry.
- `dispatch_entry_create_solid_palette_state_active` and `dispatch_entry_create_solid_palette_state` validate `0..0x3f` RGB inputs, fill a scratch `0x100`-entry palette buffer with that solid color, and build the same `0x051e` runtime-state dispatch entry, again split into active-marking and quiet variants.
- Additional caller-side comments were added instead of speculative renames on:
- `000d:84f4` (current-palette dispatch entry paired with a second object of type `0x68bf` through `entity_pair_sync_b`)
- `000d:89c6` (parameterized current-palette runtime-state wrapper with active-state flags)
- Conservative conclusion:
- seg137 is now materially beyond a foothold: it contains a coherent palette-write and palette-backed dispatch-entry emission family tied to the same runtime-state constructor lane.
- The remaining uncertainty is higher-level script/event meaning, especially the paired `0x68bf` object and the exact role of the `0004:5ad4-5b6e` caller sequence, not the local palette-helper mechanics.
### Follow-up: seg005 startup/display orchestration and seg136 active dispatch entry
- A new direct MCP recovery pass stabilized one high-value handoff path and its nearby active-dispatch helpers:
- `0004:60c0-0004:621a` recovered as `FUN_0004_60c0` with a decompiler comment summarizing the orchestration flow.
- `000d:7600-000d:760d` created and renamed to `active_dispatch_entry_mark_enabled`.
- `000d:760e` renamed to `active_dispatch_entry_mark_disabled`.
- `000d:761c` renamed to `active_dispatch_entry_create_default`.
- Current verified behavior from direct MCP decompile/disassembly:
- `FUN_0004_60c0` is a recovered startup/display orchestration path: it performs broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, runs the seg137 palette and dispatch-entry helper family, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into the still-unrecovered `0004:1e00` routine.
- The old `0004:5ad4-5b6e` caller sequence is now confirmed as one internal sub-sequence within that larger recovered function rather than an isolated orphan.
- `active_dispatch_entry_create_default` allocates or reuses a `0x42`-byte dispatch entry, stamps type `0x687f`, installs a callback-table pointer through `0x39ca`, sets event type `0x248`, sets update period `0x1e`, marks the entry as the global active dispatch entry at `0x6828`, toggles the local `+0x40` state byte through `active_dispatch_entry_mark_enabled` and `active_dispatch_entry_mark_disabled`, then enables the timer wrapper.
- `active_dispatch_entry_mark_enabled` and `active_dispatch_entry_mark_disabled` are tiny helpers that set or clear `g_active_dispatch_entry_farptr[+0x40]` respectively.
- Conservative conclusion:
- seg005 now has its first strong high-value foothold in a startup/display handoff path, even though the downstream `0004:1e00` target still needs recovery and naming.
- seg136 now has a concrete active-dispatch-entry foothold rather than being empty ledger space.
### Follow-up: seg005 large runtime/display handoff body recovered at `0004:1e00`
- `0004:1e00-0004:2420` has now been recovered as a real function object in Ghidra as `FUN_0004_1e00` with an evidence-preserving comment, replacing the earlier no-function gap.
- Current verified behavior from the recovered body:
- It begins by forcing an all-black palette through `vga_palette_set_all_black`, then performs several startup/display setup calls before manipulating the active dispatch entry at `0x6828`.
- It constructs two animation-side objects through `animation_ctor_variant_b` (`000e:2860`) using DS-local descriptors at `0x04ae` and `0x04b2`, then waits on global words around `0x8a94-0x8a98` when the alternate startup flag path is active.
- It conditionally calls `sprite_node_get_or_traverse` (`000a:b988`) after a seg122 helper path, toggles seg064 gate helpers, and then enters a larger resource/object processing region that uses globals rooted at `0x4aa`, `0x4ac`, and `0x7e22`.
- The mid-body is now better classified as a non-return transition driver rather than a generic handoff stub: it branches on the returned `SI` state after the sprite/object traversal, with one path performing the fuller runtime/display setup, one path taking a small local special-case handler (`0004:2661`), and one path marking the active dispatch entry before calling `runtime_callback_object_teardown_once(1)`.
- The `SI == 2` special-case branch is now slightly tighter: its local helper `0004:2661` forwards into `FUN_0004_25df`, which is a small type-stamped dispatch-entry constructor that allocates when needed, runs `entity_dispatch_entry_init`, stamps type `0x04b6`, and stores the caller-supplied mode/state word at `+0x32`.
- The fuller setup path clears and restores active-dispatch state, calls through the `0x2bd8` object vtable, restores the live palette through `0009:6f5a`, re-runs render/dispatch rectangle helpers (`entity_conditional_render_dispatch`, `entity_rect_compare_and_dispatch`), and finishes through the seg126 trampoline `thunk_callf_0000_ffff_000c_82f9`.
- The recovered tail confirms a clean end at `0004:2420`, with the next separate function beginning at `0004:2421`.
- Conservative conclusion:
- The main blocker on seg005 is no longer structural recovery; it is naming the exact state entered by this now-navigable startup/display transition driver.
- The presence of `animation_ctor_variant_b`, palette forcing, active-dispatch toggles, sprite-node traversal, and the `0x2bd8` vtable lane makes this look more like a real mode/state transition than a one-off helper, but the exact gameplay, intro, or front-end label still needs one more caller/data pass.
### Follow-up: seg126 wrappers feeding the `0004:1e00` transition lane
- Two previously unbounded seg126 callers around the recovered seg005 handoff are now real functions in Ghidra:
- `FUN_000c_7412` (`000c:7412-000c:7432`)
- `FUN_000c_c9f4` (`000c:c9f4-000c:ca1c`)
- The larger fallthrough body rooted at `000c:c890` is now also recovered as a real function object: `FUN_000c_c890` (`000c:c890-000c:c9f3`).
- Current verified behavior from direct MCP recovery/decompile:
- `FUN_000c_7412` is a compact wrapper into the seg005 transition lane: it clears the redraw state on the sprite/object pair rooted at `0x5e82:0x5e84`, forces a black palette through `vga_palette_set_all_black`, runs seg126 pre-entry state prep through `FUN_000c_c9f4`, then tail-calls `FUN_0004_1e00`.
- `FUN_000c_c9f4` is a short pre-entry state wrapper: it runs local seg126 setup helpers, repeatedly executes a local prep loop while state byte `0x62fe` is clear and fallback word `0x31a2` is zero, then dispatches into local helper `000c:c890` before returning to callers that continue into `FUN_0004_1e00`.
- `FUN_000c_c890` is the main seg126 pre-entry preparation body behind that wrapper: it releases up to two tracked object pairs at `0x8c5c` and `0x8c60`, conditionally frees the local pair at `0x6301:0x6303`, runs palette/render reset, conditionally constructs animation state through `animation_ctor_variant_a` on `DS:0x6341` when `0x844` and `0x62fe` are both set, marks the active dispatch entry, primes sprite redraw state, drains the event queue, and zeroes `0x8a94-0x8a98` before returning.
- This is now enough to tie the seg076 caller at `000c:742c` to the same startup/display transition lane already reached from `FUN_0004_60c0`.
- Conservative conclusion:
- seg126 now has a real foothold in the startup/runtime entry path rather than only isolated thunks and trampolines.
- The unresolved part is the exact meaning of the local state bytes and object lanes around `0x62fe`, `0x31a2`, `0x8c5c`, `0x8c60`, and `DS:0x6341`, not whether the wrapper lane itself is real.
### Follow-up: `ASYLUM.24` vs nearby `ASYLUM` ordinals
- `ASYLUM.24` remains unresolved by name, but its call pattern is now narrower.