Refactor Ghidra instructions, add new binary files, and enhance decompilation notes

- Updated Ghidra instructions to emphasize keeping analysis batches small.
- Added new binary files: `db.104.gbf`, `db.105.gbf`, and `db.27.gbf`.
- Expanded decompilation notes for `cheat_code_check`, detailing its internal workings and verified cheat actions.
- Revised segment coverage ledger to reflect new findings and promote segments from `Foothold` to `Partial`.
- Enhanced `plan-mid.md` with updated estimates and focus areas for ongoing analysis.
This commit is contained in:
MaddoScientisto 2026-03-21 21:43:33 +01:00
commit 3d4c4933ec
7 changed files with 167 additions and 26 deletions

View file

@ -6,7 +6,6 @@ applyTo: "**"
- Active target is the raw full-EXE Ghidra program `CRUSADER-RAW.EXE` unless explicitly stated otherwise.
- Use Ghidra MCP tools for analysis, decompilation, renaming, comments, and xref work.
- Keep analysis batches small: prefer 1-5 functions, labels, or comments at a time.
- Avoid speculative renames. Prefer names that are supported by one of these:
- verified raw mapping from standalone segment work
- direct string evidence

View file

@ -667,7 +667,64 @@ All 35+ identified functions renamed and annotated in Ghidra.
| Address | Name | Description |
|----------|---------------------------|-------------|
| `0x2420` | `entity_command_dispatch` | Dispatches player commands to target entity; reads 0x27d0, 0x2de4, sends events 0x14/0xf, handles state machine 0x27ca/0x27cd/0x27ce |
| `0x279a` | `cheat_code_check` | Checks entity byte+1 vs cheat sequence at 0x2833 (counter 0x283d); on full match, toggles 0x844/0x6045 and spawns vtable 0x287b/0x2892 |
| `0x279a` | `cheat_code_check` | Checks entity byte+1 against a five-byte event-code table at 0x2833 (`50 80 3e fd 27 00`, counter 0x283d); on full match, toggles 0x844/0x6045, emits helper/event code `0x103`, and takes one of two local success-side dispatch paths based on the new toggle state |
### Follow-up: `cheat_code_check` internals
- Direct raw-EXE recovery now tightens the cheat path substantially:
- `cheat_code_check` in `CRUSADER-RAW.EXE` is `0007:0d0a-0007:0e08`.
- It has exactly one direct caller in this build: `FUN_0007_04dc` at `0007:0511`, which prepares a small local event record and then calls `cheat_code_check` before continuing normal input dispatch.
- The byte compared on each call is `entity_or_event_record[+1]`, not a character fetched from a typed string buffer.
- Ghidra-side cleanup is now applied inside the function too: the decompiler shows `input_event_record`, `input_event_offset`, `new_cheat_enabled`, and `cheat_status_display_root`, plus a function comment describing the matched sequence and the cheat-toggle side effects.
- Variable and constant roles from the recovered body:
- `0x2833` is a local byte table, not an ASCII string in the raw EXE. The first five bytes are `50 80 3e fd 27`, followed by a `0x00` terminator. The checker walks that table one byte at a time.
- `0x283d` is the current match index into that byte table. On a successful byte match it increments; on mismatch it resets to zero and immediately retries the current input byte against the first table byte, so overlapping prefixes still work.
- `entity[+1]` is the compared input/event token. Because the cheat table is non-ASCII bytes, this path is matching higher-level input/event codes rather than literal typed letters.
- `0x844` is the main cheat-enable flag. The success path toggles it by converting the current byte to a boolean and negating it (`0 -> 1`, non-zero -> `0`).
- `0x6045` is written with the same post-toggle value as `0x844`, so it is a mirrored or secondary cheat-state latch rather than an independent control.
- Constant `0x103` is pushed into the shared helper at `000a:5276` immediately after the toggle. Existing notes already tie that constant to event `0x42f`; the local meaning is “emit the cheat-toggle side effect” rather than part of the matcher itself.
- `0x8c52` is forced to `1` on success before the side-effect path continues.
- Success-path structure:
- After a full table match, the code resets `0x283d` to zero, sets `0x8c52 = 1`, toggles `0x844` and `0x6045`, and calls the shared `0x103` helper.
- It then branches on the new cheat state. The `cheats-on` path uses local code pointer `DS:0x287b`; the `cheats-off` path uses `DS:0x2892`.
- Those values are not text strings and not vtable IDs. They land inside local helper code around `entity_registry_decrement` (`0007:286d`) and `entity_sprite_move_delta` (`0007:2884`), then pass through `display_null_check_dispatch` (`000b:1446`) and `sprite_node_get_or_traverse` (`000a:b988`). The exact UI/presentation object built by that path is still open, but it is clearly a local success-side dispatch path rather than “spawn vtable 0x287b/0x2892”.
- Conservative conclusion:
- `cheat_code_check` is a compact stateful matcher over event-code bytes, not text input.
- The interesting remaining question is what upstream input normalization turns user actions into the five-byte sequence `50 80 3e fd 27`, and what exact presentation object or notification path the two success-side helper targets construct for cheats-on versus cheats-off.
### Follow-up: cheat-enable sources and verified cheat-only actions
- Two independent cheat-enable sources are now verified in this build:
- The hidden input matcher in `cheat_code_check` toggles `0x844` and `0x6045` after matching the five-byte event-code table at `0x2833`.
- The command-line parser at `0004:635c-0004:63b8` recognizes the literal switch `-laurie` and directly sets `0x844 = 1` before taking a local notification path. This is the clearest readable cheat-enabler in the raw EXE.
- `jassica16` is still not directly visible in the raw EXE:
- No literal `jassica` string is present in the current string table, while `-laurie` is present as plain text.
- The matcher table remains the raw byte sequence `50 80 3e fd 27 00`, so if `jassica16` is a real player-facing input for this build it must be produced by an upstream normalization/compression step rather than stored as literal text.
- That upstream producer is still open; current evidence does not justify claiming a direct byte-for-character correspondence.
- A plain `F10` cheat action is now verified in the low-level keyboard path:
- `seg001_input_keyboard_handler` at `0006:ec29` handles input byte `0x44` and immediately returns unless cheats are enabled through `0x6045`.
- That branch does not test a modifier bit before the cheat action, so the code currently supports “plain F10 when cheats are enabled” much more strongly than “Ctrl+F10”.
- The branch emits event `0x261`, refreshes the active `0x7e22` entity/object lane, rebuilds or destroys several linked entities, and fires the follow-up event batch `0x33d`, `0x33f`, `0x340`, `0x341`, `0x33e` before re-enabling channels `4`, `1`, and `0`.
- The exact gameplay-side names of those follow-up events are still open, but this is consistent with a substantial restore/reset path such as the reported full-heal/resurrect action. No separate god-mode latch has been found in this branch.
- `FUN_0007_04dc` exposes a second cluster of cheat-only hotkeys once the same cheat gate is open:
- Confirmed byte tests in the caller-side dispatch are `0x37`, `0x4a`, `0x4e`, `0x52`, `0x53`, `0x0f`, `0x24`, plus the already-visible character events `'9'` and `'R'`.
- `0x37` calls `000c:8072`, while `0x4a` calls the neighboring helper at `000c:81c0`.
- `000c:8072` cycles a small `1..5` selector, writes that choice into the per-entity `0x7e1e` table at field `+0x15` through `FUN_0006_162d`, chooses one of the small sprite/state IDs `0x2e`, `0x2f`, `0x24`, `0x25`, and then calls `entity_table_set_sprite` at `0007:14af`.
- `000c:81c0` walks a broader `0x0b..0x19` selector range and writes the chosen value into the same per-entity table at field `+0x19` through `FUN_0006_1671`.
- Together these two helpers look like cheat/debug selector tooling tied to the current `0x7e22` object lane, not to health, invulnerability, or the cheat-enable toggle itself.
- `0x4e`, `0x52`, and `0x53` all route through the same object-method dispatch on the currently selected `0x49fb` entry after building a shared argument block with `func_0x000b2e00`, which again looks like debug/view tooling rather than a passive status flag.
- The UI/event layer also exposes multiple cheat-gated overlay or visualizer toggles behind internal event codes:
- Event `0x441` reaches `000c:8e16` and toggles byte `0xee0` before refreshing the `0x2bd8` controller object.
- Event `0x241` reaches `000c:8e46` and toggles byte `0x2bc9` before the same refresh.
- Event `0x141` reaches `000c:8e72` and toggles byte `0x2bca` before the same refresh.
- Event `0x410` reaches `000c:9703` and toggles byte `0x604f`, then takes one of two display/notification paths.
- Events `0x142` and `0x143` also dispatch into large cheat-gated view-building paths at `000c:9154` and `000c:92cd`; they clearly redraw substantial display state, but their exact user-facing names remain open.
- Current bottom line on the folklore claims:
- “Cheats can be enabled with `-laurie`” is directly verified.
- “There is a hidden input-sequence cheat enabler” is directly verified, but its exact human-readable spelling is still unresolved at the binary level.
- “F10 performs a large cheat-only restore/reset action” is directly verified.
- “Ctrl+F10 enables god mode” is not supported by the current code path; the verified F10 branch does not require a modifier and no dedicated god-mode latch has been recovered yet.
- “Other cheat-only debug visualizers/tools exist” is directly verified, though several are still known only by internal event codes or selector helpers rather than final user-facing names.
### Menu / Event Callbacks
@ -1023,7 +1080,10 @@ A scroll/camera management cluster found in the `0007:bxxx0007:dxxx` range.
| Address | Name | Evidence |
|---------|------|---------|
| `0007:bab5` | `entity_set_watch_ptr` | Stores FAR entity ptr to `0x2bd8` (the watch target entity). |
| `0007:ba00` | `watch_entity_controller_create_global` | Thin global-create wrapper around `watch_entity_controller_create`; allocates default controller object and stores it at `0x2bd8`. |
| `0007:ba13` | `watch_entity_controller_dispatch_if_present` | If `0x2bd8` is non-null, calls controller vtable slots `+0x2c` and `+0x30`. |
| `0007:ba45` | `watch_entity_controller_create` | Allocates/initializes a type `0x2c2b` controller object, stores it at `0x2bd8`, sets event type `0x0219`, and installs callback-table entry `0x2be4` through `0x39ca`. |
| `0007:bab5` | `entity_set_watch_ptr` | Legacy name still in place, but newer constructor evidence now shows `0x2bd8` is a controller object lane rather than just a raw watched-entity FAR pointer. |
| `0007:baea` | `camera_update_and_check_player_scroll` | Calls watch entity vtable `+0x24`; if `0x2bd1` flag clear checks if player position (from `g_player_entity_farptr+0x40`) has moved > 32 units since `0x2be0`; if so, updates `0x2be0` and conditionally dispatches scroll event via `0x45aa`. |
| `0007:c6ba` | `scroll_camera_set_state_params` | Stores word params to `0x8354`, `0x8356`, byte to `0x8358`; dispatches. |
| `0007:cd56` | `dispatch_if_flag_2bd3_set` | Returns unless `0x2bd3` non-zero. |
@ -1076,7 +1136,6 @@ A scroll/camera management cluster found in the `0007:bxxx0007:dxxx` range.
|---------|------|---------|
| `0007:a96d` | `entity_copy_string_truncated80` | Strlen(param_3) ≤ 0x50 guard; copies string word-by-word from param_3 into `param_2+8`. |
| `0007:b813` | `memcpy_4words` | Copies 4 words (8 bytes) from `param_2` to `param_1`. |
| `0007:ba45` | (unnamed) | Null/non-null far-ptr dispatch: different thunk paths based on `param_2 == 0`. |
| `0007:b46d` | `entity_dispatch_if_slot82e2_valid` | Guard: if `*(int *)0x82e2 != -1`, calls dispatch thunk. |
### Linked List Utilities (draw pool + sprite)
@ -1102,7 +1161,7 @@ A scroll/camera management cluster found in the `0007:bxxx0007:dxxx` range.
| `0x2bca/0x2bc9` | `g_option_toggle_state` | UI option toggle state flags |
| `0x2bd1` | `g_scroll_block_flag` | Blocks camera update path if non-zero |
| `0x2bd3` | `g_scroll_active` | Non-zero = scroll system active |
| `0x2bd8` | `g_watch_entity_ptr` | FAR ptr to entity being tracked by camera |
| `0x2bd8` | `g_watch_entity_controller_farptr` | FAR ptr to the watch/camera controller object created by `watch_entity_controller_create`; older notes treating it as a raw entity pointer were too narrow |
| `0x2be0` | `g_player_scroll_pos` | Cached player world X+Y (ulong) for scroll threshold detection |
| `0x8354..0x8358` | `g_scroll_state_params` | Three scroll state params (word, word, byte) |
@ -2025,16 +2084,73 @@ Current structural read of the cache globals:
- 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`).
- `transition_preentry_run_until_complete_or_abort` (`000c:c9f4-000c:ca1c`)
- The larger fallthrough body rooted at `000c:c890` is now also recovered as a real function object: `transition_preentry_release_resources` (`000c:c890-000c:c9f3`).
- The previously blocked seg126 helpers are now also recovered as standalone functions after overlap repair in the surrounding namespace:
- `transition_preentry_setup_resources` (`000c:c63a-000c:c88f`)
- `transition_preentry_step_script` (`000c:ca1d-000c:cd52`)
- 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.
- `transition_preentry_run_until_complete_or_abort` is the short pre-entry state wrapper: it runs local seg126 setup helpers, repeatedly executes a local prep loop while local ready byte `0x62fe` is clear and external gate word `0x31a2` remains zero, ticks the local palette-fade controller on each pass, then dispatches into `transition_preentry_release_resources` before returning to callers that continue into `FUN_0004_1e00`.
- `transition_preentry_release_resources` is the cleanup/finalize body behind that wrapper: it releases the paired temporary presenter objects at `0x8c5c` and `0x8c60`, conditionally frees the local stream buffer at `0x6301:0x6303`, runs palette/render reset, conditionally constructs animation state through `animation_ctor_variant_a` on local storage `DS:0x6341` when `0x844` and `0x62fe` are both set, then immediately marks byte `+0x40` on the shared global owner at `0x6828`, marks the active dispatch entry, primes sprite redraw state, drains the event queue, and zeroes `0x8a94-0x8a98` before returning.
- `wait_for_vga_vertical_retrace` (`000c:c62c-000c:c639`) is now recovered as a real helper: it polls VGA status port `0x3da` until the vertical-retrace edge and is called from both the seg126 pre-entry loop and nearby fade/update paths.
- `transition_preentry_setup_resources` captures baseline coordinates into `0x8c58:0x8c5a`, constructs two paired temporary presenter objects at `0x8c5c:0x8c5e` and `0x8c60:0x8c62` through `000a:9748` with preset IDs `0x10` and `0x11`, stages the local stream buffer at `0x6301:0x6303`, seeds palette/render state, and resets the neighboring control globals at `0x62fa-0x6318` before returning.
- The shared seg088 helpers behind that object family are now tighter too: `text_renderer_measure_string_width` (`000a:30aa`) returns width through the object's virtual measure path, and `text_renderer_draw_string_at` (`000a:30d7`) stores x/y into the object and draws a null-terminated string. That is enough to reclassify `0x8c5c` and `0x8c60` as a paired temporary text-renderer lane rather than generic sprite/object state.
- `transition_preentry_step_script` early-outs while fade state `0x630a` is active or when the tracked object position at `0x2de4+0x40/+0x42` is unchanged, decrements the local countdown at `0x62fa:0x62fc`, interprets the `0x6301` byte stream with control values including `0x21`, `0x23`, `0x24`, `0x26`, `0x2a`, `0x40`, and `0x5e`, uses `0x62ff` as the stream cursor, uses the temporary text-renderer objects at `0x8c5c` and `0x8c60` for layout/render work, sets ready flag `0x62fe` on the `0x23` case, uses `0x6305` as a one-shot redraw latch around the `0x26`/`0x2a` control path, and leaves `0x630a` / `0x630b` to the neighboring palette-fade controller.
- Direct follow-up on the old `0x31a2` ambiguity now points away from local script state: `event_queue_state_reset` at `0008:89c1` clears `0x31a2`, interrupt-side queue code around `0008:a283` / `0008:a314` increments and decrements it, and several busy-wait helpers (`000c:e4d8`, `000c:e546`, `000c:e5c6`) spin on it. In the seg126 wrapper this makes `0x31a2` a plausible external input/event break gate rather than part of the local pre-entry bytecode interpreter.
- The seg136 owner flag at `g_active_dispatch_entry_farptr[+0x40]` is now less abstract too. `active_dispatch_entry_mark_enabled` / `active_dispatch_entry_mark_disabled` still force it high or low during entry setup, but nearby seg136 helpers also copy it into fresh entries, clear it when the current owner becomes inactive, and decrement it only while `0x31a2 > 0`. That ties the `0x6828` owner lane more directly to the same external input/event gate that seg126 polls.
- The same segment now also has a clearer transition-control shell around that prep body:
- `thunk_callf_0000_ffff_000c_827d` is the pre-transition side that restores redraw/event state, runs the `0x2bd8` controller callbacks, redraws the `0x5e82:0x5e84` sprite/object pair, and leaves local state bytes set for the subsequent `FUN_0004_1e00` call.
- `thunk_callf_0000_ffff_000c_82f9` is the post-transition side that resets the slot table through `FUN_0008_39e9`, clears local state bytes, runs the `0x5e82:0x5e84` cleanup path, and returns the lane to its quiescent state.
- `FUN_000c_834a` is a small guard wrapper that conditionally calls `FUN_000c_8231()` when gate byte `0x85f` is set; this same helper is used at the start of `FUN_0004_1e00` and in the local seg126 caller family.
- 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.
- seg126 is now beyond a mere foothold: it contains a coherent transition-entry and transition-exit control lane around the seg005 startup/display state, with pre-entry prep, guarded entry, post-transition cleanup, and local state/fade integration.
- The unresolved part is the exact higher-level UI/transition role of the paired text-renderer lanes at `0x8c5c` and `0x8c60`, the precise event semantics of external gate `0x31a2`, and the exact relationship between local animation storage `DS:0x6341` and the shared global owner at `0x6828` whose `+0x40` byte follows that same gate, not whether the transition lane itself is real.
### Follow-up: seg127 palette fade controller tied to the same transition lane
- The nearby seg127 state/fade controller is now anchored by verified functions in Ghidra:
- `palette_fade_begin_full_up` (`000c:c600-000c:c615`)
- `palette_fade_begin_full_down` (`000c:c616-000c:c62b`)
- `transition_palette_fade_tick` (`000c:cd53-000c:cd75`)
- `transition_palette_fade_begin` (`000c:cd76-000c:cddd`)
- `transition_palette_fade_out_step` (`000c:cdde-000c:ce56`) and `transition_palette_fade_in_step` (`000c:ce57-000c:cecb`)
- Current verified behavior from direct MCP recovery/decompile:
- `palette_fade_begin_full_up` and `palette_fade_begin_full_down` are fixed-range wrappers over `transition_palette_fade_begin`: both use the full `0x80`-entry palette range with step size `4`, differing only in direction/state (`2` for up, `1` for down).
- The current local callers are now visible too: `000c:cd1a` invokes `palette_fade_begin_full_up` for the `ES:[DI] == 0x26` case and sets `0x6305 = 1`, while `000c:cd3f` and `000c:cb06` invoke `palette_fade_begin_full_down` when the fade controller is idle.
- `transition_palette_fade_begin` takes a palette source pointer, start index, count, step amount, and direction/state, stores them into local controller state at `0x630e-0x6316`, sets active flag byte `0x630a = 1`, stores the direction/state word at `0x630b`, then runs the local prep helper and one immediate fade step.
- `transition_palette_fade_tick` is the small controller gate over that state: it returns when inactive, dispatches to `transition_palette_fade_out_step` when `0x630b == 1`, and to `transition_palette_fade_in_step` when `0x630b == 2`.
- `transition_palette_fade_out_step` and `transition_palette_fade_in_step` iterate over palette range `[0x6312 .. 0x6312 + 0x6314)`, writing VGA DAC entries through ports `0x3c8/0x3c9` from the source palette at `0x630e:0x6310` while updating brightness offset byte `0x630d` by step `0x6316`; both clear active flag `0x630a` when the fade reaches its terminal black or full-bright state.
- This controller is tied directly into the transition lane already under study: `transition_preentry_run_until_complete_or_abort` calls `transition_palette_fade_tick`, and `transition_preentry_setup_resources` seeds the neighboring seg126 controller state at `0x62fa-0x6318` before the script interpreter starts using it.
- Conservative conclusion:
- seg127 is no longer just a foothold; it is a real palette fade controller subsystem adjacent to the same startup/display entry path, with verified initializer, dispatcher, step bodies, fixed-range wrappers, and caller-side state gating.
- The remaining question is not the local fade mechanics, but which exact transition states and tracked objects choose the fade direction and palette source.
### Follow-up: seg049 watch/camera controller object at `0x2bd8`
- The longstanding `0x2bd8` watch/camera lane is now tighter and partially corrected from older notes: it is not just a raw watched-entity pointer, but a real controller object lane.
- Current verified behavior from direct MCP recovery/decompile:
- `watch_entity_controller_create` (`0007:ba45`) allocates or reuses an object, runs the local constructor path at `000a:8627`, stamps type `0x2c2b`, stores the resulting FAR pointer globally at `0x2bd8`, sets event type `0x0219`, and installs callback target `0x2be4` through the callback table at `0x39ca`.
- `watch_entity_controller_create_global` (`0007:ba00`) is a thin wrapper that constructs the default global controller and stores the returned FAR pointer at `0x2bd8:0x2bda`.
- `watch_entity_controller_dispatch_if_present` (`0007:ba13`) is the paired non-null dispatcher that calls controller vtable slots `+0x2c` and `+0x30` when the global exists.
- Existing callers in the seg005 transition lane (`FUN_0004_1e00`) call through the same `0x2bd8` vtable `+0x2c` slot before palette restore and post-transition redraw prep, which strengthens the interpretation that this is a real watch/camera-side controller object participating in display-state transitions.
- Conservative conclusion:
- seg049 now has a real foothold in the watch/camera controller subsystem.
- The remaining ambiguity is the exact distinction between the controller object at `0x2bd8` and the ultimately watched entity or map object it may point at or manage, not whether the controller itself is real.
### Follow-up: seg108 sprite/object flag lane at `0x4f38`
- The global sprite/object lane at `0x4f38:0x4f3a` now has two recovered flag helpers in addition to the existing redraw/timer sync evidence:
- `sprite_object_clear_flag40_if_present` (`000b:2b08-000b:2b1f`)
- `sprite_object_set_flag40_if_present` (`000b:2b20-000b:2b37`)
- Current verified behavior from direct MCP recovery/decompile:
- `sprite_object_clear_flag40_if_present` checks whether the global sprite/object FAR pointer at `0x4f38` is non-null and clears bit `0x40` in the word at object offset `+0x32`.
- `sprite_object_set_flag40_if_present` performs the same guard and sets bit `0x40` at that same `+0x32` word.
- This fits the already-confirmed sync behavior in `sprite_node_get_or_traverse`, where `0x4588` callback emissions are immediately mirrored through `FUN_000b_1e39` using the global sprite/object pointer. Together, that narrows `0x4f38` to an active sprite/object instance whose state bits are toggled during the same startup/display transition path that uses `FUN_0004_60c0` and `FUN_0004_1e00`.
- Conservative conclusion:
- seg108 now has a real foothold in the active sprite/object state lane.
- The remaining ambiguity is what bit `0x40` means semantically and how the `0x4f38` object relates at a higher level to the `0x2bd8` watch/camera controller and the `0x4588` callback object.
### Follow-up: `ASYLUM.24` vs nearby `ASYLUM` ordinals

View file

@ -47,7 +47,7 @@
"46","code","0x7A200","0x7DC","None","","","","crusader_ne_segments.csv"
"47","code","0x7AC00","0x9B4","None","","","","crusader_ne_segments.csv"
"48","code","0x7B800","0x63","None","","","","crusader_ne_segments.csv"
"49","code","0x7BA00","0x1E3F","None","","","","crusader_ne_segments.csv"
"49","code","0x7BA00","0x1E3F","Foothold","Watch/camera controller object lane","watch_entity_controller_create_global; watch_entity_controller_create; watch_entity_controller_dispatch_if_present; entity_set_watch_ptr","Exact controller-vs-watched-entity ownership still needs caller-side confirmation, but 0x2bd8 is now clearly a real controller object lane","crusader_decompilation_notes.md; plan-mid.md"
"50","code","0x7DE00","0x9C8","None","","","","crusader_ne_segments.csv"
"51","code","0x7EA00","0x1D02","None","","","","crusader_ne_segments.csv"
"52","code","0x80A00","0x1D65","None","","","","crusader_ne_segments.csv"
@ -106,7 +106,7 @@
"105","code","0xAEC00","0x9F6","None","","","","crusader_ne_segments.csv"
"106","code","0xAF800","0x1795","None","","","","crusader_ne_segments.csv"
"107","code","0xB1400","0x40C","None","","","","crusader_ne_segments.csv"
"108","code","0xB1A00","0x113F","None","","","","crusader_ne_segments.csv"
"108","code","0xB1A00","0x113F","Foothold","Active sprite/object state lane","sprite_object_clear_flag40_if_present; sprite_object_set_flag40_if_present","Higher-level meaning of bit 0x40 and its relation to 0x2bd8 and 0x4588 is still unresolved","crusader_decompilation_notes.md; plan-mid.md"
"109","code","0xB2E00","0x1424","None","","","High-value gap around 000b:2e00 still unresolved","crusader_ne_segments.csv; crusader_decomp_progress.md"
"110","code","0xB4400","0x4C4","None","","","","crusader_ne_segments.csv"
"111","code","0xB4A00","0x489","None","","","","crusader_ne_segments.csv"
@ -124,8 +124,8 @@
"123","code","0xC3C00","0xE6D","None","","","","crusader_ne_segments.csv"
"124","code","0xC4E00","0x3DD","None","","","","crusader_ne_segments.csv"
"125","code","0xC5400","0x1A3E","None","","","","crusader_ne_segments.csv"
"126","code","0xC7400","0x402A","Foothold","Runtime-entry wrappers and pre-entry state prep","FUN_000c_7412; FUN_000c_c890; FUN_000c_c9f4; thunk_callf_0000_ffff_000c_827d","Broader seg126 state-machine/helper meanings remain open, but this lane now clearly feeds the seg005 startup/display transition","crusader_decompilation_notes.md; plan-mid.md"
"127","code","0xCC600","0x8F6","None","","","","crusader_ne_segments.csv"
"126","code","0xC7400","0x402A","Partial","Transition-entry wrappers, pre-entry setup/script, and exit control","FUN_000c_7412; transition_preentry_setup_resources; transition_preentry_release_resources; transition_preentry_run_until_complete_or_abort; transition_preentry_step_script; wait_for_vga_vertical_retrace; thunk_callf_0000_ffff_000c_827d; thunk_callf_0000_ffff_000c_82f9; FUN_000c_834a","The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, an external input/event gate at 0x31a2, and the shared active-dispatch owner at 0x6828 whose +0x40 byte follows that same gate; remaining open work is the exact UI role of the renderer pair, the DS:0x6341 to 0x6828 animation-owner relationship, and the separate oversized overlap rooted at 000c:db68","crusader_decompilation_notes.md; plan-mid.md"
"127","code","0xCC600","0x8F6","Partial","Palette fade controller and transition-state gate","palette_fade_begin_full_up; palette_fade_begin_full_down; transition_palette_fade_begin; transition_palette_fade_tick; transition_palette_fade_out_step; transition_palette_fade_in_step","Exact transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, and active/direction state at 0x630a/0x630b are now clear","crusader_decompilation_notes.md; plan-mid.md"
"128","code","0xCD200","0x5D0","None","","","","crusader_ne_segments.csv"
"129","code","0xCDA00","0xD77","None","","","","crusader_ne_segments.csv"
"130","code","0xCEA00","0x47D","None","","","","crusader_ne_segments.csv"

1 Segment Type FileOffset Length CoverageStatus KnownSubsystem KeyNamedFunctions Blockers NotesSource
47 46 code 0x7A200 0x7DC None crusader_ne_segments.csv
48 47 code 0x7AC00 0x9B4 None crusader_ne_segments.csv
49 48 code 0x7B800 0x63 None crusader_ne_segments.csv
50 49 code 0x7BA00 0x1E3F None Foothold Watch/camera controller object lane watch_entity_controller_create_global; watch_entity_controller_create; watch_entity_controller_dispatch_if_present; entity_set_watch_ptr Exact controller-vs-watched-entity ownership still needs caller-side confirmation, but 0x2bd8 is now clearly a real controller object lane crusader_ne_segments.csv crusader_decompilation_notes.md; plan-mid.md
51 50 code 0x7DE00 0x9C8 None crusader_ne_segments.csv
52 51 code 0x7EA00 0x1D02 None crusader_ne_segments.csv
53 52 code 0x80A00 0x1D65 None crusader_ne_segments.csv
106 105 code 0xAEC00 0x9F6 None crusader_ne_segments.csv
107 106 code 0xAF800 0x1795 None crusader_ne_segments.csv
108 107 code 0xB1400 0x40C None crusader_ne_segments.csv
109 108 code 0xB1A00 0x113F None Foothold Active sprite/object state lane sprite_object_clear_flag40_if_present; sprite_object_set_flag40_if_present Higher-level meaning of bit 0x40 and its relation to 0x2bd8 and 0x4588 is still unresolved crusader_ne_segments.csv crusader_decompilation_notes.md; plan-mid.md
110 109 code 0xB2E00 0x1424 None High-value gap around 000b:2e00 still unresolved crusader_ne_segments.csv; crusader_decomp_progress.md
111 110 code 0xB4400 0x4C4 None crusader_ne_segments.csv
112 111 code 0xB4A00 0x489 None crusader_ne_segments.csv
124 123 code 0xC3C00 0xE6D None crusader_ne_segments.csv
125 124 code 0xC4E00 0x3DD None crusader_ne_segments.csv
126 125 code 0xC5400 0x1A3E None crusader_ne_segments.csv
127 126 code 0xC7400 0x402A Foothold Partial Runtime-entry wrappers and pre-entry state prep Transition-entry wrappers, pre-entry setup/script, and exit control FUN_000c_7412; FUN_000c_c890; FUN_000c_c9f4; thunk_callf_0000_ffff_000c_827d FUN_000c_7412; transition_preentry_setup_resources; transition_preentry_release_resources; transition_preentry_run_until_complete_or_abort; transition_preentry_step_script; wait_for_vga_vertical_retrace; thunk_callf_0000_ffff_000c_827d; thunk_callf_0000_ffff_000c_82f9; FUN_000c_834a Broader seg126 state-machine/helper meanings remain open, but this lane now clearly feeds the seg005 startup/display transition The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, an external input/event gate at 0x31a2, and the shared active-dispatch owner at 0x6828 whose +0x40 byte follows that same gate; remaining open work is the exact UI role of the renderer pair, the DS:0x6341 to 0x6828 animation-owner relationship, and the separate oversized overlap rooted at 000c:db68 crusader_decompilation_notes.md; plan-mid.md
128 127 code 0xCC600 0x8F6 None Partial Palette fade controller and transition-state gate palette_fade_begin_full_up; palette_fade_begin_full_down; transition_palette_fade_begin; transition_palette_fade_tick; transition_palette_fade_out_step; transition_palette_fade_in_step Exact transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, and active/direction state at 0x630a/0x630b are now clear crusader_ne_segments.csv crusader_decompilation_notes.md; plan-mid.md
129 128 code 0xCD200 0x5D0 None crusader_ne_segments.csv
130 129 code 0xCDA00 0xD77 None crusader_ne_segments.csv
131 130 code 0xCEA00 0x47D None crusader_ne_segments.csv

View file

@ -31,25 +31,51 @@ The estimates below are intentionally conservative. They measure verified behavi
- seg137 is now promoted from `Foothold` to `Partial`: direct MCP recovery stabilized a coherent palette/dispatch-entry helper family with safe renames for all-black, all-white, arbitrary-RGB, grayscale, black-state, and solid-color state builders around the same `entity_dispatch_entry_init_runtime_state` lane. The remaining gap is the higher-level event/script meaning of those helpers, not the local mechanics.
- seg005 and seg136 now have new high-value footholds: `FUN_0004_60c0` is recovered as a startup/display orchestration handoff that drives the seg137 palette helper family, validates an object through vtable `+0x0c`, creates the default active dispatch entry, programs mouse state, and then hands off into `0004:1e00`; nearby seg136 helpers are now stabilized as `active_dispatch_entry_mark_enabled`, `active_dispatch_entry_mark_disabled`, and `active_dispatch_entry_create_default`.
- The downstream seg005 handoff body is now also classified further: `FUN_0004_1e00` (`0004:1e00-0004:2420`) is a non-return startup/display transition driver with confirmed use of `vga_palette_set_all_black`, `animation_ctor_variant_b`, `sprite_node_get_or_traverse`, seg064 gate helpers, the `0x2bd8` vtable lane, and the `0x4aa/0x7e22` resource/object lane. The remaining work is naming the exact state label, not repairing the structure.
- seg126 now has a deeper foothold instead of only wrapper coverage: `FUN_000c_7412`, `FUN_000c_c9f4`, and the newly recovered `FUN_000c_c890` now show a coherent pre-entry preparation lane that releases tracked objects, resets palette/render state, conditionally constructs animation state at `DS:0x6341`, and then feeds the same `FUN_0004_1e00` startup/display transition from the seg076 side.
- seg126 is now promoted from `Foothold` to `Partial`: `FUN_000c_7412`, `transition_preentry_setup_resources`, `transition_preentry_release_resources`, `transition_preentry_run_until_complete_or_abort`, `transition_preentry_step_script`, `thunk_callf_0000_ffff_000c_827d`, `thunk_callf_0000_ffff_000c_82f9`, and `FUN_000c_834a` now show a coherent pre-entry, guarded-entry, script/fade step, and post-transition control shell around the same `FUN_0004_1e00` startup/display state.
- seg127 is now promoted from `Foothold` to `Partial`: `palette_fade_begin_full_up`, `palette_fade_begin_full_down`, `transition_palette_fade_begin`, `transition_palette_fade_tick`, `transition_palette_fade_out_step`, and `transition_palette_fade_in_step` form a concrete local palette-fade controller with verified full-range wrappers and caller-side state gating immediately beside the same seg126/seg005 transition lane.
- seg049 is no longer blank: `watch_entity_controller_create_global`, `watch_entity_controller_create`, and `watch_entity_controller_dispatch_if_present` now show that `0x2bd8` is a real type-stamped watch/camera controller object lane rather than only a raw watched-entity pointer, and that same controller is exercised from `FUN_0004_1e00`.
- seg108 is no longer blank: `sprite_object_clear_flag40_if_present` and `sprite_object_set_flag40_if_present` now anchor the `0x4f38` global sprite/object lane as a real state-bit-controlled object path used beside the same `0x4588` callback sync and startup/display transition flow.
- Direct MCP follow-up on seg126 and seg127 now recovered the missing helper bodies after boundary repair: `transition_preentry_setup_resources` (`000c:c63a`), `transition_preentry_release_resources` (`000c:c890`), `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`), `transition_preentry_step_script` (`000c:ca1d`), and the neighboring `transition_palette_fade_tick` / `transition_palette_fade_begin` / `transition_palette_fade_out_step` / `transition_palette_fade_in_step` chain are now named against verified behavior. The latest semantic pass also tightened the two main open globals: `0x8c5c` / `0x8c60` are now best understood as a paired temporary text-renderer lane, while `0x31a2` behaves like an external input/event break gate maintained by queue/interrupt-side code. The remaining structural cleanup is the separate oversized overlap rooted at `000c:db68`, not the seg126 helper family.
- Bonus cheat-lane cleanup is now visible in Ghidra too: `cheat_code_check` has recovered local names (`input_event_record`, `input_event_offset`, `new_cheat_enabled`, `cheat_status_display_root`) and a decompiler comment stating that it matches the five-byte event-code sequence `50 80 3e fd 27 00` before toggling the cheat-state bytes and taking one of two local notification paths.
### Current Focus
1. Finish Priority 0 refinement by promoting more exact segment rows where notes already support a verified foothold.
2. Continue the Priority 1 pass by tracing the higher-level startup/display callers, branch outcomes, and pre-entry object lanes that stitch the seg137 palette helper family into the wider `0x4588` / dispatch-entry object-role lane.
2. Continue the Priority 1 pass by tracing the higher-level startup/display callers, branch outcomes, pre-entry object lanes, palette-fade ownership, watch/camera controller ownership, and active sprite/object ownership that stitch the seg137 palette helper family into the wider `0x4588` / dispatch-entry object-role lane.
### Next Resume Point
1. Classify the remaining seg126 pre-entry object lanes around `FUN_000c_c890`, especially tracked pairs `0x8c5c`, `0x8c60`, local state gates `0x62fe` / `0x31a2`, and animation buffer/object `DS:0x6341`.
2. Continue caller-role classification inside `entity_cleanup_resources_and_dispatch` (contains both `000d:9d5e` and `000d:a3b7`) and map how it relates to `FUN_000d_938c`, `FUN_0004_60c0`, `FUN_000c_7412`, `FUN_000c_c890`, and the seg136/seg137 active-dispatch helper family.
3. Clarify the object validated through `FUN_0004_60c0` vtable slot `+0x0c` and how it relates to the sprite/object lane at `0x4f38`, the `0x2bd8` vtable callbacks used inside `FUN_0004_1e00`, and the tracked object pairs released by `FUN_000c_c890`.
4. Revisit `allocator_phase_finalize_pass` only where it intersects the same callback object semantics, rather than broad allocator mechanics that are already sufficiently constrained.
5. Continue `ASYLUM.24` only after the `0x4588` / dispatch-entry lane and `0004:1e00` transition path have no further cheap wins.
1. Keep classifying the seg126 pre-entry text-renderer lane around `transition_preentry_setup_resources`, `transition_preentry_step_script`, and `transition_preentry_release_resources`, especially by:
- comparing more preset `0x10` / `0x11` text-renderer callsites,
- tracing who owns the rendered buffer loaded into `0x6301:0x6303`,
- mapping the control bytes `0x21` / `0x23` / `0x24` / `0x26` / `0x2a` / `0x40` / `0x5e` to concrete display behavior,
- and deciding whether the paired `0x8c5c` / `0x8c60` lane is a title/body pair, normal/highlight pair, or another fixed UI pairing.
2. Finish the `0x31a2` gate pass as one batch:
- classify the read sites at `0004:c24d`, `000c:ca11`, `000c:e4d8`, `000c:e546`, `000c:e5c6`, `000d:9304`, `000d:b6b1`, and `000d:c0ee`,
- relate them back to interrupt-side updates at `0008:a283` / `0008:a314`,
- and decide whether `0x31a2` is best described as user-acknowledge, queued-input depth, or a broader event-break gate.
3. Tighten the `DS:0x6341` to `0x6828` relationship:
- compare the seg126 `animation_ctor_variant_a` call with the other raw callsites at `0005:3c4f`, `0005:3c74`, `000c:6176`, and `000c:619c`,
- map who owns `g_active_dispatch_entry_farptr[+0x40]`,
- and classify whether seg126 is constructing a transition-local animation payload for the shared active dispatch entry or only toggling an owner-side state bit after setup.
4. Identify which higher-level transition states own the seg127 fade-controller inputs at `0x630a-0x6316` and how that fade state is chosen from the seg005/seg126 startup path.
5. Repair the still-oversized overlap rooted at `000c:db68` only if it blocks follow-on analysis or decompiler visibility in the same transition lane.
6. Clarify the relationship between the seg049 watch/camera controller at `0x2bd8`, the seg108 sprite/object lane at `0x4f38`, and the object validated through `FUN_0004_60c0` vtable slot `+0x0c`.
7. Continue caller-role classification inside `entity_cleanup_resources_and_dispatch` (contains both `000d:9d5e` and `000d:a3b7`) and map how it relates to `FUN_000d_938c`, `FUN_0004_60c0`, `FUN_000c_7412`, `transition_preentry_release_resources`, and the seg136/seg137 active-dispatch helper family.
8. Keep the cheat/input side lane warm when it offers cheap wins:
- identify the upstream producer for the five-byte cheat event-code sequence `50 80 3e fd 27`,
- resolve the exact success-side presentation path behind `DS:0x287b` versus `DS:0x2892`,
- finish naming the verified cheat-only actions now that plain `F10` is confirmed in `seg001_input_keyboard_handler`,
- map the remaining caller-side hotkey bytes in `FUN_0007_04dc` (`0x37`, `0x4a`, `0x4e`, `0x52`, `0x53`, `0x0f`, `0x24`, `'9'`, `'R'`) to final user-facing controls,
- verify whether the reported `H` / hack-mover description belongs to this build or to a higher translation layer,
- and tie the cheat toggle flags `0x844` / `0x6045` into the wider input/event-dispatch system, especially the cheat-gated overlay events `0x141`, `0x142`, `0x143`, `0x241`, `0x410`, and `0x441`.
9. Revisit `allocator_phase_finalize_pass` only where it intersects the same callback object semantics, rather than broad allocator mechanics that are already sufficiently constrained.
10. Continue `ASYLUM.24` only after the `0x4588` / dispatch-entry lane and `0004:1e00` transition path have no further cheap wins.
### Headline Estimate
- Overall useful decompilation progress: about 30%
- Reasonable uncertainty band: about 25% to 35%
- Overall useful decompilation progress: about 35%
- Reasonable uncertainty band: about 30% to 40%
This is the best single-number estimate for the full game right now.
@ -58,8 +84,8 @@ This is the best single-number estimate for the full game right now.
| Metric | Estimate | Meaning |
|---|---:|---|
| Top 100 far-call target coverage | about 80% | Roughly 80 of the top 100 most-called far-call targets have been named or materially classified |
| Whole-program behavioral coverage | about 30% | Verified subsystem and function understanding across the executable |
| Segment spread with meaningful analysis | about 14% to 20% | Segments with more than a trivial foothold or isolated note |
| Whole-program behavioral coverage | about 35% | Verified subsystem and function understanding across the executable |
| Segment spread with meaningful analysis | about 19% to 25% | Segments with more than a trivial foothold or isolated note |
| Tooling maturity for continued work | about 75% | Core repair, lookup, and fallback automation needed for continued progress |
### Why These Numbers Differ