Crusader_Decomp/plan-mid.md
2026-04-12 14:45:08 +02:00

461 lines
149 KiB
Markdown

# Crusader Decompilation Mid-Project Plan
## Purpose
This file is the live mid-project tracker for the Crusader decompilation effort.
Keep it focused on:
1. current verified state,
2. active blockers,
3. next resume work,
4. and the remaining path to a reasonably complete decompilation.
Detailed completed analysis belongs in the files under `docs/`, not in this plan.
## Progress Snapshot
Latest verified batch: [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now includes a 2026-04-12 live MCP CLUT override routing closure pass on active `SLUS_002.68` centered on `0x80041458`, `0x80041144`, `0x80044bdc`, `0x80044e9c`, `0x800a9f48`, and `0x800a9f66`. Current best read is now exporter-critical and path-explicit: main-visible injects authored palette token while special-visible does not; submitter override gate is shared (`flags & 0xfffffff0`); and active override resolution diverges by submitter/resource-format lane (image-table and sprite format-2 use `psx_clut_override_table_by_palette_token[token]`, sprite non-format-2 uses token as a row key into `psx_clut_table_by_resource_bank`). Practical consequence is that token `0` is effectively no-override for this world-object path and exporter CLUT logic must branch by route lane plus submitter/resource format instead of flattening token handling. Live artifacts in this batch are targeted comments at `0x800415b0`, `0x800412d0`, `0x80044e10`, and `0x80044eb8`.
Latest verified batch: [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now includes a 2026-04-12 live MCP wall-family discriminator pass for the exporter regression where atlases repeat and wall faces collapse. Current best read is now split-explicit for `0x003e..0x004f`: constructor bind in `psx_object_create_simple_record`/`psx_object_create_compound_record` still converges on per-type `DAT_800758d8[type]`, while real divergence happens post-bind through selector install (`0x800260e8`), frame-token latch (`0x80025d68` -> `obj+0x94`), and stage-1 versus stage-2 route semantics (`0x80041458` vs `0x80041144`), including main-visible-only authored palette-token injection for `>=0x003e`. Immediate exporter consequence is to prioritize effective route/latch-state discrimination over inventing a new pre-constructor resource-bank split. Live artifacts in this batch are targeted comments at `0x80046038`, `0x80026100`, `0x80041554`, and `0x80040f88`.
Latest verified batch: [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now includes the 2026-04-12 VRAM-proof palette/export follow-up for `map_renderer/src/lib/psx-cache.js`, `src/config.js`, and `src/vue/controller/scene-presentation.js`. Current best read is now rule-explicit: `mode 1` PSX bundles should not trust bundle header palette index `+0x14` as the rendered selector, they should decode against one shared contiguous 256-entry CLUT equivalent to live VRAM row `0xF0`, `x=0`, with the old header value preserved only as diagnostic `defaultPaletteIndex`. The same batch also closes the static-export omission: the processed PSX catalog already contained `62` maps, so the "single map" symptom was export inclusion/config rather than cache enumeration.
Latest verified batch: [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now includes the 2026-04-12 focused no-placeholder exporter follow-up for `map 104` in `map_renderer/src/lib/psx-cache.js` plus renderer provenance surfacing in `src/vue/controller/scene-presentation.js`. Current best read is now exporter-explicit: mixed-role unresolved buckets no longer fall back to synthetic atlases, they resolve per authored-family + raw-`u5` cohort into actual PSX bundle art with preserved `mappingSource` / `artCohort` provenance. Focused validation rebuild now exports `1002` art items (`52` roots + `950` constructors), `0` fallback items, `1` atlas, and `136` shape definitions for scene fingerprint `3497e7f641856415`.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes the 2026-04-12 final live map-104 cohort closure pass on active writable `SLUS_002.68` anchored to scene fingerprint `3497e7f641856415` (`0x0042` roots `0x0022` items `25/35`, roots `0x0030` items `30/31`, constructor `0x0030` items `85/86`, and control `0x0066` item `53`) with focused create/update/draw/control inspection at `0x800249f4`, `0x80024eec`, `0x800131a8`, `0x80025d68`, `0x80041458`, `0x80041144`, `0x8002be6c`, `0x80013618`, and `0x80013688`. Current best read is now exporter-actionable: authored route seed split (`0x0022` vs `0x0030`) remains the strongest safe first-key divider; root-`0x0030` and constructor-`0x0030` cohorts still fail to diverge at creation and should not be split by origin alone; sampled `bit0x0400` and policy/runtime captures remain non-discriminating for this anchor set; and the immediate safe exporter change is route-seed bucketization for unresolved `0x0042` placeholders with conservative hold on deeper heuristics until runtime diagnostics sample concrete non-null values. Live artifacts in this batch are targeted decompiler comments at `0x800249f4`, `0x80024eec`, `0x800131a8`, `0x80025d68`, and `0x80041458`.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 focused live runtime/control-island policy pass on active writable `SLUS_002.68` centered on `0x80063e54`, `0x80063e68`, `0x800675ec`, and `0x800675f8` with routing/draw/order consumers at `0x800131a8`, `0x80041458`, and `0x8002bf0c`. Current best read is now split-explicit: island tables are control/runtime gating structures and per-type policy modifies ordering/render/publication behavior, but stage-1 vs stage-2 visible lane choice still hinges primarily on object-local route bit `obj+0x1c & 0x0400` and submitter choice remains resource-kind based. Live artifacts in this batch are six conservative helper renames (`0x8002e598`, `0x8002e484`, `0x8002e498`, `0x8002e3e8`, `0x800308ac`, `0x800304c4`) plus targeted comments at `0x80039fd8`, `0x80013518`, `0x80013550`, `0x8004161c`, `0x8002bf2c`, and `0x80034d60`.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 focused live loader/install pre-constructor closure pass on active writable `SLUS_002.68` centered on `0x80039444`, `0x8003917c`, `0x80045ffc`, `0x8002badc`, and `0x80040768`. Current best read is now install-boundary explicit: WDL load installs type art/state lanes and section-pack pointers before root dispatch; CLUT install is explicit in `level_palette_header_apply`; detached runtime-stream payload install is explicit in `psx_install_level_audio_runtime_stream_bundle`; and constructors therefore start with preinstalled art/state/policy/CLUT context instead of raw section-0 rows alone. Live artifacts in this batch are one conservative function rename (`0x80040768`) plus five targeted decompiler comments across loader/install entry points.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 focused live draw-submission closure pass on active writable `SLUS_002.68` centered on `0x8004137c`, `0x80041458`, `0x80041144`, `0x80040d44`, `0x80044bdc`, and `0x80044e9c`. Current best read is now submission-explicit: both world-visible lanes select submitter by bound resource kind (`kind==5` image-table else sprite), both consume live frame token `obj+0x94`, and CLUT resolution converges on `psx_clut_table_by_resource_bank` with optional `psx_clut_override_table_by_palette_token[(flags>>8)]`. The strongest lane split is palette-token injection: main-visible ORs authored high-byte token (`source+0x06` or `+0x0c`) into submit flags, while special-visible does not, giving an executable-backed discriminator for unresolved placeholder-family export behavior.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 focused live selector/transition pre-latch closure pass on active writable `SLUS_002.68` centered on `0x80018578`, `0x8001bca0`, `0x8001e6e8`, `0x800260e8`, and `0x80025d68` with direct row bytes from `0x80063c1c` and `0x80063d68`. Current best read is now stage-explicit: type-`0x0042` pre-latch reseat is early-gated by view margin and object lane bit `0x0020`; transition row lookup remains `DAT_80063a00` -> `DAT_80063b4c`; selector `3/4` effects route through `psx_object_select_state_script` install (`obj+0x9e`) before final live frame-token latch at `obj+0x94` in `psx_object_advance_state_script`. Live artifacts in this batch are five targeted decompiler comments at `0x80018578`, `0x8001bca0`, `0x8001e6e8`, `0x800260e8`, and `0x80025d68`, plus a new exporter implication section for map-104 placeholder-family splitting by pre-latch selector versus latched frame token.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 focused live type-art install and constructor-binding closure pass on active writable `SLUS_002.68` centered on `0x80045ffc`, `0x800249f4`, `0x80024eec`, `0x80038f18`, and `0x80041458` with globals `0x800758d8/0x800758c8`. Current best read is now lane-explicit from load/install to draw: install writes active-header slot then resolves kind-4/5 resource and commits built-resource cache; constructors consume active-header by type and either reuse built-resource (kind 5) or build per-instance resource; draw uses ctor-bound `obj+0x10` resource and live `obj+0x94` frame token with submitter selected by resource kind. Live artifacts in this batch are five targeted decompiler comments at `0x80045ffc`, `0x800249f4`, `0x80024eec`, `0x80038f18`, and `0x80041458` to preserve exporter-relevant semantics for unresolved map-104 families.
Latest verified batch: [docs/psx/map-storage-model.md](docs/psx/map-storage-model.md) now includes a 2026-04-12 live section-0 authored-family descriptor-dispatch closure on active `SLUS_002.68` centered on `0x800256b0`, `0x800258cc`, and descriptor table lane `0x80063118/0x80063220/0x800626f8`. Current best read is now convergence-explicit: unresolved families `0x0042`, `0x0049`, and `0x0055..0x0063` share descriptor row `0x800626f8` (slot0 `0x80013618`, slot1 `0x80013688`, slot2 `0x800254c8`) for both root-dispatch and constructor-placement section-0 records, so type divergence should be pursued in per-type banks/policy/state lanes rather than in section-0 descriptor callback identity.
Latest verified batch: [docs/psx/map-storage-model.md](docs/psx/map-storage-model.md) now includes a 2026-04-12 live object-creation/state-selection closure pass on active `SLUS_002.68` centered on `psx_object_create_simple_record`, `psx_object_create_compound_record`, `psx_spawn_compound_record_advance_state_once`, `psx_spawn_simple_record_set_active_flag`, `psx_object_select_state_from_transition_table`, `psx_object_advance_state_script`, and `psx_type42_transition_selector_tick`. Current best read is now chain-explicit from authored row to visible frame: constructors copy authored `u5` into `obj+0x1c` and seed selector via `u4`; transition/reselection lanes can mutate selector/low control bits; `psx_object_advance_state_script` latches final live frame token into `obj+0x94`; projection/draw consume `obj+0x94` directly through `psx_resource_frame_*` helpers with submit path chosen by bound resource kind. Live artifacts in this batch are one helper rename (`0x8003a37c -> psx_queue_global_draw_tint_pulse_once`), two supporting data labels (`0x80067544`, `0x80067614`), and targeted decompiler comments across spawn/select/latch/projection entry points.
Latest verified batch: [docs/psx/map-storage-model.md](docs/psx/map-storage-model.md) now includes a 2026-04-12 live loader/storage ownership clarification pass on active `SLUS_002.68` centered on `wdl_resource_bundle_load_by_index` (`0x80039444`), `psx_lzss_unpack_into_level_buffer` (`0x8003b00c`), `psx_lzss_pack_level_buffer` (`0x8003aba8`), and `psx_load_type_state_banks` (`0x8003917c`). Current best read is now ownership-explicit in extractor terms: `0x80067838` is promoted to `psx_level_section_pack_base`, `0x800676d8` to `psx_level_clut_table_ptr`, the `0x3e00` compressed/decompressed lane (`0x8006b5d8` -> `0x8006769c`) is confirmed as persistent runtime substrate with save-side repack parity, and constructor consume-site `0x80024c60` now explicitly links loader-installed `psx_type_simple_component_bank[type]` payloads to live behavior program fields.
Latest verified batch: [docs/psx/map-storage-model.md](docs/psx/map-storage-model.md) now includes a 2026-04-12 live marker/control runtime-island clarification pass on active `SLUS_002.68` centered on `0x80063e54`, `0x80063e68`, `0x800675ec`, and post-load mode-action sequencing at `0x80039ef4`. Current best read is now control-explicit: the selector/map tables are post-load and slot-gating control structures (not direct art lanes), runtime block persistence is guarded by snapshot sentinels (`0x80031878`/`0x80031a3c`), and a previously anonymous bounded marker-action queue family (`0x80030cf0/0x80030dfc/0x80030ed4/0x80030ebc`, count at `0x80067798`) now has conservative live names/comments to preserve queue-mediated transition behavior.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now includes a 2026-04-12 live visibility-routing and draw-lane clarification pass on active `SLUS_002.68` centered on `psx_object_integrate_motion_and_route_visible`, `psx_main_visible_list_sort_range`, `psx_main_visible_order_graph_unlink_pair`, `psx_main_visible_order_graph_detach_object`, `psx_draw_main_visible_object`, `psx_draw_special_visible_queue`, `psx_sprite_resource_submit_frame`, and `psx_image_table_submit_frame`. Current best read is now rule-explicit: stage-1 routes through main-visible list/sort and stage-2 routes through the special-visible queue; ordering uses dependency-graph links with policy-bit influence (`0x0008`, `0x0600`); submitter choice is strictly resource-kind (`kind==5` image-table else sprite); main-visible applies authored palette-token overrides while special-visible does not; and submitters converge on CLUT selection through `psx_clut_table_by_resource_bank` with optional override via `psx_clut_override_table_by_palette_token[(flags>>8)]`.
Latest verified batch: [docs/psx/map-storage-model.md](docs/psx/map-storage-model.md) now includes a 2026-04-12 live runtime-bank/art-install clarification pass on active `SLUS_002.68` centered on `0x8003917c`, `0x80045ffc`, constructor consumers, and type-bank globals `0x800758c8/cc/d0/d4/d8` plus policy table pointer `0x800675f8`. Current best read is now role-split and loader-to-renderer explicit: state-bank installs seed scripts/components/extents; art installs resolve active-header and built-resource lanes with post-install alias behavior; and policy-table install at load bridges into world draw/order/interaction consumers. Live artifacts in this batch are two evidence-backed renames (`0x8003917c -> psx_install_type_state_script_component_extents_banks`, `0x80045ffc -> psx_install_type_art_active_header_and_built_resource`) plus targeted decompiler comments at `0x800396cc`, `0x800399b4`, `0x8003970c`, `0x800399f4`, `0x80039ad0`, `0x8003977c`, `0x800460c8`, `0x800460d4`, `0x800398f0`, and `0x80041604`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live countdown-versus-slot frame-order closure centered on map-54 split `0x800208f0` and world-frame call order at `0x8002b830`. Current best read is now ordering-explicit: countdown terminal mode split/write runs before behavior and deferred-control lanes that can reach slot-`0x0f` arm sink `0x800232f0`, so boundary timing pressure is strongest, failure-side suppression is plausible, and plain countdown-success direct causality remains weakest. Live artifacts in this batch are conservative decompiler comments at `0x8002b830`, `0x80020900`, and `0x800232f0`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live authored-producer reachability correction pass centered on `0x80026710`, `0x8002677c`, `0x80027ecc`, and `0x800214ac`. Current best read is now guard-corrected: `0x80026710` bounds record arg-count (`word0-1 < 0x0a`), not opcode value, so opcode `54 -> subop 49 -> sink 0x800214ac` remains a viable authored-program lane. Practical consequence is that strongest candidate producer context is again the type-state behavior record stream seeded from `psx_type_simple_component_bank[type]`, with tuple `(0x0f,0x0a,0x04)` classified as authored-static source data transported through optional runtime arg-index remap.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live progression-latch timing pass centered on `0x80027548`, `psx_map_progression_table`, and map-54 boundary transitions. Current best read is now ordering-explicit: `0x80027548` stages `next_map` via `psx_level_session_set_next_map_id` into deferred latch `DAT_800678d0`, `current_map_id` only commits at session rollover (`0x80031edc`), and natural tuple miss risk is therefore a deferred preemption window (`54 -> 55` rollover before slot-`0x0f` `(0x0a,0x04)` emit), not a same-tick overwrite race.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live synthesis-only conservative-stabilization pass for the natural in-level JL-9 event lane. Current best read is now wording-stable without overreach: callback entries `0x8002745c`/`0x80027548` stay classified as indirect callback-table targets (no direct callers), countdown tick `0x80020794` stays classified as world-frame control timing with explicit map-54 boundary split, and live comments now explicitly separate timing/context evidence from still-unproven direct tuple-production proof.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live callback-table ownership pass on region `0x800641f0..0x80064220` for entries `0x8002745c` and `0x80027548`. Current best read is now owner-explicit: this region is part of `psx_behavior_opcode_handler_table` (`DAT_800641ac`) dispatched by `psx_object_behavior_opcode_dispatch` (`0x8002677c`), with exact indices `0x15` and `0x19`; selection is opcode-driven from behavior program words, while the only proven active caller lane still enforces `(opcode_word-1) < 0x0a` at `0x80026710`, so these entries remain topology/timing evidence rather than the strongest direct proven producer lane for slot-`0x0f` arm. Live artifacts in this batch are conservative decompiler comments at `0x8002685c`, `0x80026710`, `0x8002745c`, and `0x80027548`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-12 live slot-`0x0f` subcase taxonomy pass beyond tuple `(0x0a,0x04)` on active `SLUS_002.68`. Current best read is now family-explicit: surrounding `0x0f` branches cluster as transition/countdown/objective-state and message lanes (`0x0a/1..3`, `0x0a/0x2e`, `0x01/1`, `0x04/1`, `0x06/0x42`), while `(0x0a,0x04)` remains the only recovered eligibility-arm write to `psx_debug_extra_channel_gate` under non-hidden header-state-3 predicates. Live artifacts in this batch are one conservative rename (`0x80049014 -> psx_audio_cdxa_select_and_play_cue`) plus targeted decompiler comments at `0x80023154`, `0x800231b0`, `0x8002321c`, `0x80023334`, `0x80023390`, `0x800236d8`, and `0x800237cc`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live transition-callback provenance pass centered on `0x8002745c` and `0x80027548`. Current best read is now provenance-explicit and timing-aware: both entries are indirect callback-table targets from region `0x800641f0..0x80064220` (no direct caller xrefs), `0x8002745c` can carry or suppress slot-`0x0f` tuple opportunity depending on branch (`DAT_80078a14==0` path calls `0x80020f7c`, alternate path skips it), and `0x80027548` progression apply (`map_progression_table[current_map_id]`) makes natural tuple firing more timing-sensitive by advancing transition state before later control-event handling.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live producer-side authored-source closure pass centered on the action-record frame consumed by `psx_level_gate_slot_dispatch_from_action_record` (`0x800214ac`). Current best read is now loader-explicit: `psx_load_type_state_banks` (`0x800391f0`) installs `psx_type_simple_component_bank[type]` from level bundle type-state payloads, object constructors seed component `program_base/pc` from that bank (`0x80024c60/0x80024c88`), and behavior tick/dispatch (`0x80026740 -> 0x8002677c -> 0x80027ecc`) transports authored operands into the sink frame. Practical consequence is that tuple `(slot,arg1,arg2)=(0x0f,0x0a,0x04)` now looks authored-static at source context with runtime index-resolution as transport, while exact emitting type-row attribution remains open. Live artifacts added in this batch are conservative decompiler comments at `0x80039250`, `0x80024c60`, and `0x80026740`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live countdown-versus-slot-family structure check driven by the timed-segment failure clue. Current best read is now causality-tight: `psx_control_event_countdown_transition_tick` (`0x80020794`) and `psx_control_event_apply_countdown_step` (`0x800205e8`) are in the world-frame countdown lane, while natural JL-9 arm remains slot-dispatch lane (`0x800214ac -> table 0x800640a0 -> slot0f 0x800230e4 -> writer 0x800232f0`) with no recovered direct countdown-to-slot call edge; practical consequence is that timer behavior is likely related by shared control-state/timing pressure rather than being the same direct feeder branch.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live multi-map last-mission chain validation pass centered on `psx_map_progression_table`, selector-to-map mapping, and late transition helpers. Current best read is now split-map explicit: map `54` is still the strongest natural selector anchor (`0x0f -> 54`), while maps `55..58` are downstream transition phases in the same slot-`0x0f` gate family (`map_to_slot[54..58]=0x0f`) with contiguous progression (`54->55->56->57->58->0xff`), which better explains the JL-9 gate-host family without displacing `54` as the most reproducible natural host.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live synthesis pass driven by two new clues (`timed failure` and `multi-map last mission`) with fresh caller/xref evidence from active `SLUS_002.68`. Current best read is now ranking-explicit: optional scripted event inside the countdown/transition chain is strongest, countdown-branch timing miss second, map-to-map transition interaction third, and plain countdown-success branch weakest; practical consequence is that next runtime validation should prioritize tuple-emission observability (`slot 0x0f`, `0x0a/0x04`) at the map-54 boundary path (`0x800208f0`) with progression callback context (`0x80027548`).
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live alternate-feeder search centered on sink targets `0x800214ac` and `0x800230e4` with explicit control/deferred/callback lane checks. Current best read is now tighter in one key way: no new direct literal/table feeder into either sink was recovered beyond `0x800636d4 -> 0x800214ac` and `0x800640dc -> 0x800230e4`, so the strongest non-`54 -> 49` candidate is currently transition/control-driven (`psx_level_gate_slot05_handler` at `0x80022068`, `psx_control_event_slot0e_handler` case-6 at `0x80023074`, and helper callsite `0x8002748c` into `psx_control_event_apply_level_channel_preset`) rather than a newly proven behavior-opcode producer path.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live level-54 boundary clue pass for natural `MFM4` failures. Current best read is now map-specific and timing-tight: `psx_control_event_countdown_transition_tick` (`0x80020794`) has an explicit split at `current_map_id 0x36` (`<=54 -> mode 0x1a`, `>54 -> mode 0x1b`), and additional optional late control lanes (`psx_level_gate_slot05_handler` `(0x0a,0x28)` plus progression callback `0x80027548`) can advance state before slot `0x0f` `(0x0a,0x04)` arm writes `psx_debug_extra_channel_gate`. Practical consequence is that natural failure is now best explained as optional-path/timing miss around map-54 late transitions, not wrong-host selection.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live slot-handler sibling recovery pass centered on `psx_level_gate_slot_handler_table` (`0x800640a0`) with newly created table-entry functions at `0x800215fc`, `0x80021810`, `0x800219e4`, `0x80021fac`, `0x80022214`, `0x800222e8`, `0x800223cc`, `0x800226e0`, `0x800227ac`, `0x80022b50`, `0x80023854`, and `0x80023af0`, plus a control-pair rename at `0x80022940`. Current best read is now sibling-explicit across slot-family `0x0a..0x0f`: adjacent `0x0a` subcases are control/message/transition lanes, while slot `0x0f` case `(0x0a,0x04)` remains the only recovered sibling branch that arms `psx_debug_extra_channel_gate` at `0x800232f0`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live upstream-producer closure pass for `psx_level_gate_slot_dispatch_from_action_record` (`0x800214ac`). Current best read is now structure-explicit: the sink consumes a pointer-frame (`record+0/+4/+8/+c`) forwarded from `psx_behavior_subopcode_dispatch` (`0x80027ecc`) as `local_60+1`, and those pointer fields are produced in `psx_object_behavior_opcode_dispatch` by either direct pointer words or index-resolved slot pointers (`base + index*4`). Practical consequence is that tuple `(slot,arg1,arg2)=(0x0f,0x0a,0x04)` is now bounded as behavior-script-produced pointer data rather than a sink-local immediate constant, while the exact shipped authored producer instance remains an open runtime-trace target.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live event-only synthesis continuation pass for the natural gate-arm lane. Current best read is now slightly tighter but still conservative: `0x800230e4` is promoted to `psx_control_event_slot0f_handler` (slot-family role, not single-branch setter), slot-table entries `0x800640d4/0x800640d8/0x800640dc` are explicitly labeled as slot `0x0d/0x0e/0x0f` handler entries, and comments at `0x80064284` and `0x800636d4` now preserve the key split between structural topology (`54 -> 49 -> sink`) and still-unproven active reachability beyond the known `<0x0a` caller lane.
Latest verified batch created a dedicated event-only working note at [docs/psx/jl-9-in-level-event.md](docs/psx/jl-9-in-level-event.md). Current best read is now organized around the natural gate-arm problem specifically: sink dispatcher `0x800214ac..0x800215f8`, slot-family handlers `0x0d/0x0e/0x0f`, exact arm tuple `(slot 0x0f, arg1 0x0a, arg2 0x04)`, host family `{54,55,56,57,58,82}`, and the still-open upstream authored producer. Practical consequence is that future JL-9 event passes should resume from the dedicated event note instead of re-mixing event-only details into the broader passcode and inventory note.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 sink-feeder-only closure pass on live `SLUS_002.68` centered on `0x800214ac..0x800215f8` with explicit argument-byte loads at `0x800215cc/0x800215e0`. Current best read is now artifact-tight: `0x800214ac` is promoted as `psx_level_gate_slot_dispatch_from_action_record`; dispatch slot is loaded from `*(*(record+0))`; handler args are loaded from `*(*(record+8))` and `*(*(record+0xc))`; and the JL-9 gate-arm tuple is bounded to slot `0x0f` with `(arg1,arg2)=(0x0a,0x04)` into `psx_set_debug_extra_channel_gate` (`0x800230e4`) through `psx_level_gate_slot_handler_table[0x0f]` at `0x800640dc`. Upstream table topology is now preserved in live comments as `psx_behavior_opcode_handler_table[54]=0x80027ecc` and `psx_behavior_subop_handler_table[49]=0x800214ac`, while active reachability from the currently proven gameplay caller path remains bounded by `(opcode_word-1) < 0x0a` at `0x80026710`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 host-level closure pass dedicated to natural JL-9 event hosting across family `{54,55,56,57,58,82}` using live table bytes and late-transition caller checks. Current best read is now ranking-explicit: map `54` remains the strongest natural host because it is the reciprocal selector-anchored slot-`0x0f` entry (`0x0f -> 0x36`), while `55..57` stay plausible transition-phase hosts, `82` remains family-valid but weakly anchored for published-code replication, and `58` is now de-prioritized as a stable host because `psx_map_progression_table[58] = 0xff` marks terminal progression pressure; practical failure model for natural `MFM4` is now timing/event-miss (missing tuple `(0x0a,0x04)` before hidden phase), not passcode decode mismatch.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live caller/context reassessment around `0x80026690` / `0x8002677c` with a sibling control-runner check. Current best read is now split cleanly between active proof and topology: the only proven caller into `psx_object_behavior_opcode_dispatch` remains the `<0x0a` bounded lane at `0x80026710`, while `54 -> 49 -> 0x800214ac` remains structurally valid table linkage but should be deprioritized as an immediate active explanation until a second caller/context lane is recovered. The same batch also lands conservative live artifacts (`0x80027ecc` function creation/name and `0x80020f7c` rename with caller-bound comments).
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live MCP late-objective pass focused on mission-complete text handlers, transition siblings, and uncommon reward-branch outcomes in the slot `0x0d`/`0x0e`/`0x0f` control-event family. Current best read is now path-tight: slot `0x0e` case `0x0a/6` is a concrete late transition that applies selector `0x0f`, selector `0x0f` is the only sampled direct selector lane into channel `0x0f`, and natural JL-9 gate arm remains bounded to slot `0x0f` case `0x0a/4` (`0x800232f0`) under `hidden==0 && runtime_header_state==3`; practical consequence is that the remaining blocker is now specific authored event provenance inside that channel-`0x0f` family, not branch existence.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live closure pass dedicated to decoding the control-event 0x0a family around `0x80022c6c..0x80023390`. Current best read is now sibling-explicit: slot `0x0d` (`0x80022c6c`) and slot `0x0e` (`0x80022ea8`) are recovered as mission-complete/control-event siblings to slot `0x0f` (`0x800230e4`), and tuple `(0x0a,0x04)` remains the strongest natural pre-hidden gate-arm branch because it is the only recovered in-family case that writes `psx_debug_extra_channel_gate` under `hidden==0 && runtime_header_state==3`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 event-only synthesis pass for the natural JL-9 in-level gate-arm lane plus a conservative live Ghidra naming/comment sweep on still-raw central entities. Current best read stays intentionally split between what is proven and what is only topology: sink-side event evidence remains strong (`0x800214ac..0x800215f8` -> `slot 0x0f` -> `0x800230e4`, tuple branch `0x0a/0x04` at `0x800232f0`), while upstream `54 -> 49` is retained as structural table linkage pending proof of a second active caller/context beyond the known `(opcode_word-1) < 0x0a` guard at `0x80026710`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 active-caller recheck focused on natural gate-arm production into sink dispatcher `0x800214ac..0x800215f8`. Current best read is now priority-corrected: sink-side tuple closure remains strong (`slot 0x0f`, `arg1 0x0a`, `arg2 0x04` into `0x800230e4`), but the only proven gameplay caller lane into `psx_object_behavior_opcode_dispatch` remains bounded by `(opcode_word-1) < 0x0a` at `0x80026710`, so the earlier `54 -> 49` path should stay as table topology and be deprioritized as an immediate active explanation until a second caller/context lane is recovered.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 user experiment follow-up that materially weakens `MFM4` as the main mystery. Current best read is now experiment-backed: a natural `MFM4` trial did **not** yield `JL-9`, while `JFM4` plus manual gate byte `0x8006739d = 0x01`, then `L0SR`, then `R1 + Circle`, still produced `JL-9`. Practical consequence is that `MFM4` remains the best natural prime candidate on code grounds, but the dominant unresolved question is now the natural in-level gate-arm event rather than the passcode choice itself. Deferred user experiments to revisit later are `2`, `4`, `5`, and `6` from the current emulator test plan.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes user-validated downstream closure for the forced JL-9 test. Current best read is now causal-explicit: manual byte poke `0x8006739d = 0x01`, then `L0SR`, then `R1 + Circle` successfully produces `JL-9`, which proves the late hidden/input grant half is correct and that the gate byte alone is sufficient for the extra `0x0d` branch once hidden trigger runs. Practical consequence is that the more direct thing being bypassed is the natural in-level gate-arm event, while `MFM4` is now demoted from “lead candidate” to “best natural prime candidate” for recreating the same latch without manual memory edits.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` hard-clear-theory check and manual gate-poke closure. Current best read is now split cleanly: the "beat the game on hard, then enter `L0SR`" story remains weak-to-medium because completion/congratulation handlers were found but no direct bridge from endgame flow into `psx_debug_extra_channel_gate` was recovered; meanwhile the practical emulator route is now strong, because `psx_debug_extra_channel_gate` is confirmed as byte `0x8006739d`, read as nonzero at `0x8002fff4`, with no recovered clear in inspected session/load/menu paths, so manual `0x01` poke + hidden `L0SR` + `R1 + Circle` is now the best forced test for the extra `0x0d` lane.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` published-mission-code sweep against the user-supplied level-code table. Current best read is now candidate-explicit: ordinary decode rows are `i=0x00..0x0e`, ordinary selector return is `s=i+1`, and first-char difficulty only writes `psx_level_runtime_header_state`. Practical JL-9 consequence is that `MFM4` (Level 15 hard) is now the only strong published-code prime recovered so far, because ordinary row `i=0x0e` yields selector `s=0x0f`, maps through `DAT_80063e54[0x0f]=0x36`, and lands in gate family `DAT_80063e68[54]=0x0f` while also setting `runtime_header_state=3`; `LRTN/MRTN/PRTN` remain strong negatives because the `?RTN` family clears header state to `0`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` reachability-correction pass on the previously claimed step-2 producer chain. Current best read is now split cleanly between topology and proof: table topology still shows `DAT_800641ac[54] = 0x80027ecc` and `DAT_80063610[49] = 0x800214ac`, but static reachability currently proves only one caller to `psx_object_behavior_opcode_dispatch` (`0x80026740`) and that path bounds `(opcode_word-1) < 0x0a` before dispatch, so high-index entries are not yet proven active from the known path. Practical consequence is that sink-side gate-writer evidence remains strong (`0x800214ac..0x800215f8` -> `DAT_800640a0[0x0f] = 0x800230e4`, args `0x0a/0x04`), while the open upstream task is now narrowed to recovering a second proven caller/context that can feed high behavior-opcode indices.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` step-2 producer-path closure pass for the gate-arm dispatcher source. Current best read is now structurally explicit upstream: `psx_object_behavior_opcode_dispatch` table index `54` reaches handler `0x80027ecc`, which secondary-dispatches through `DAT_80063610[subop]`; sub-op `49` maps to `0x800214ac`, which then performs the level-gated slot dispatch into `DAT_800640a0[0x0f] = 0x800230e4` with arg bytes from record pointers (`0x800215cc/0x800215e0`). Practical consequence is that step-2 is now identified as an in-level behavior/control opcode event lane (`54 -> 49`), with the remaining open item narrowed to one concrete authored mission/object instance rather than the producer subsystem itself.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` NRTN/?RTN decode-to-apply closure over `0x8003ec8c`, `0x80034c14..0x80034ddc`, and `0x80021138`. Current best read is now delay-slot exact: special index `0x0f` clears `psx_level_runtime_header_state` but returns raw selector `0x10`, eval maps through `DAT_80063e54[0x10]=0x3f`, and caller therefore does not take the `s0==0` skip; apply executes selector-`0x10` case at `0x8002142c` (writes `DAT_800675e4=0x1d`, common helper `a0=0x36`). Practical JL-9 consequence remains strict: NRTN/?RTN does not itself satisfy gate-arm predicate `runtime_header_state==3` and cannot alone coexist with the required non-hidden gate-arm state.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now records a 2026-04-11 correction to the earlier Mission 16 / developer-office theory. Current best read is that the attractive `DAT_80063e54[0x0f] = 0x36 -> DAT_80063e68[0x36] = 0x0f` chain was the wrong table index for the runtime `?RTN` path; the live caller path uses returned selector `0x10`, maps through `DAT_80063e54[0x10] = 0x3f`, and only later executes selector-`0x10` apply logic with common-helper `a0 = 0x36`. Practical consequence is narrower: office-like content may still be involved, but current static evidence no longer proves that `NRTN/?RTN` places the session directly inside the slot-`0x0f` gate family.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` hypothesis check for the exact `NRTN -> in-level office event -> ?0SR/L0SR -> input 0x1e` recipe. Current best read is now classification-explicit: the two-phase structure remains code-backed (`pre-hidden gate-arm at 0x800232f0` then hidden/input trigger at `0x80013154..0x80013174`), dispatch evidence still supports a real in-level slot-`0x0f` gate-arm path via `0x800214ac..0x800215f8` into `0x800230e4`, but the exact player-visible office event and deterministic `NRTN` role are still not singularly closed by static evidence; practical status is therefore `plausible but unproven` rather than contradicted.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` clarification pass on what user-facing "step 2" actually means. Current best read is now much less misleading: the gate-arm write is not a second passcode-screen action but an in-level scripted/control dispatch routed through `0x800214ac..0x800215f8`, where `DAT_800640a0[0x0f] = 0x800230e4` (`psx_set_debug_extra_channel_gate`) and the exact writer tuple is `(slot 0x0f, param_2 0x0a, param_3 0x04)`; meanwhile hidden `?0SR` / `L0SR` still uses the shared passcode decoder but returns selector `0`, so it does not follow the ordinary nonzero mission/apply-load branch. Practical consequence is that the current JL-9 recipe is now explicitly `normal passcode prime -> in-level scripted gate-arm event -> hidden passcode -> R1+Circle`, with the remaining blocker narrowed to naming the concrete in-level event.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` step-2 caller narrowing pass on the gate-arm writer path (`0x800230e4` -> `0x800232f0`). Current best read is now dispatch-concrete: reachability is through an indirect level-gated dispatcher at `0x800214ac..0x800215f8` (`DAT_80063e68[current_level]` compare + `DAT_800640a0[a2]` handler table), with slot `0x0f` resolving to `psx_set_debug_extra_channel_gate`; handler arguments are loaded from action-record byte pointers; and the exact gate-arm tuple is tightened to `(dispatch index 0x0f, param_2 0x0a, param_3 0x04)`, narrowed to a small level-scripted family where `DAT_80063e68 == 0x0f` (`54..58,82`) rather than a single globally named menu callback.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` gate-survivability closure pass across passcode application, level-load/session loops, and menu-reset helpers. Current best read is now lifecycle-explicit: `psx_debug_extra_channel_gate` (`0x8006739d`) remains one-writer/one-reader (`0x800232f0` set, `0x8002fff4` read) with no recovered static clear in inspected load/menu paths; hidden decode (`0x8003ed28`) still returns selector `0` and skips normal apply/load branch checks around `0x80034d84`; and practical two-phase ordering remains `non-hidden gate arm first -> hidden/input trigger later`, with step 2 supported after a level load in the same running session.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` passcode-screen semantics closure for the normal-versus-hidden entry question and the JL-9 sequence wording check. Current best read is now user-flow explicit: normal and hidden passcodes both route through `psx_passcode_screen_eval_current_entry`/`psx_passcode_decode_to_mission_selector`; hidden `0x10` still sets `psx_hidden_passcode_flag` in that same decoder; and the unnamed caller block around `0x80034c14` has distinct immediate branch behavior for eval return `0` versus nonzero before transition/setup calls. Practical consequence is that the existing JL-9 sequence remains directionally right on two-phase logic (`pre-hidden gate arm` then hidden/input trigger) but is now marked operationally under-specified until one live trace pins the exact player-visible producer for `param_2==0x0a,param_3==4` into `psx_set_debug_extra_channel_gate`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` gate-arm caller-path closure around `psx_set_debug_extra_channel_gate` (`0x800230e4`) and the exact writer at `0x800232f0`. Current best read is now sequence-tight: the writer executes only in the `param_2==0x0a,param_3==4` branch; the store requires `psx_hidden_passcode_flag==0 && psx_level_runtime_header_state==3`; and the required `runtime_header_state==3` comes from the normal passcode first-char delta branch at `0x8003ed58` rather than special hidden/sibling passcode branches. Practical closure remains two-phase (`normal non-hidden state-3 prime -> gate-arm event -> hidden passcode -> input code 0x1e`) with one bounded remaining gap: naming the exact player-visible producer for the `0x0a/4` event inside the nearby undefined dispatch block.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` RP-16 startup/default follow-up that pushed on undefined nearby init stubs, startup mode-action dispatch callsites, selected-id writer closure, active-channel writes, and level mode/difficulty tables. Current best read is tighter and still negative for startup RP-16: `committed_selected_item_id` has only two recovered writers (`0x80039f68` reset-to-zero and `0x8002f170` table-commit sink), startup dispatch remains `8 -> (optional 2) -> 4` without a direct commit call, and scanned `channel_commit_row_selected_item_id` rows (`0x00..0x19`, table at `0x80064355` with selected byte at `+9`) contain no `sel=0x01`. Practical classification remains `RP-16 not a proven startup/default weapon` while preserving `0x01` as a real non-startup row.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` special-passcode priming closure pass over `?0SR/?RTN/?QQQ` versus `psx_level_runtime_header_state`, `psx_hidden_passcode_flag`, and `psx_debug_extra_channel_gate`. Current best read is now branch-tight: `?RTN` (`0x0f`) clears runtime-header state to `0`, `?0SR` (`0x10`) sets hidden flag to `1`, `?QQQ` (`0x11`) returns sentinel `0x12` without priming runtime-header state, and none of these specials can independently satisfy gate-arm (`hidden==0 && header_state==3`) at `0x800232f0`; practical JL-9 closure remains a two-phase flow where gate-arm is pre-hidden and hidden/input trigger occurs afterward.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a strict executable-only 2026-04-11 JL-9 validation lane over passcode decode, input-code mapping, gate write, grant call, and selected-id commit sink. Current best read is now narrowed to one concrete blocker for an exact user-facing recipe: code proves `hidden index 0x10 arm` + `input code 0x1e` + `gate read at 0x8002fff4` + late `unlock(0x0d)` and commit sink at `0x8002f168/0x8002f170`, but still does not independently map one deterministic player-visible action that drives the `0x800232f0` writer path (`psx_set_debug_extra_channel_gate`, `param_2='\\n' case 4`) under `hidden==0 && runtime_header_state==3`. Practical next closure is therefore one live caller-arg probe on `0x800230e4`/`0x800232f0`, not another broad sweep.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` lifecycle closure pass for `psx_debug_extra_channel_gate` (`0x8006739d`) and `psx_hidden_passcode_flag` (`0x80067454`) across session init, passcode decode/arm, and debug grant entry. Current best read is now explicit and condition-tight: hidden flag has bounded set/clear points (`0x8003ed28`/`0x8002bab8` set, `0x8002b9e4` timed clear), extra gate remains one-writer/one-reader with no recovered static clear (`0x800232f0` set under `hidden==0 && header_state==3`, `0x8002fff4` read for late `0x0d` unlock), and the strongest practical JL-9 sequence is now a two-phase A->B->input flow where gate-arm occurs pre-hidden and grant trigger occurs during hidden-mode timer.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` RP-16 startup/default-init closure pass over post-load reset, mission/loadout init, difficulty/mode-transition apply, and selected-id writer coverage. Current best read is now explicit for the startup question: `psx_level_post_load_runtime_reset` clears `committed_selected_item_id` to `0` before dispatching mode actions, `psx_weapon_channels_init_mode_loadout` and `psx_weapon_channels_apply_mode_transition_state` contain no fixed selected-id `0x01` seed, and recovered fixed-immediate commit callsites (`0x11`, `0x12`, and one context-specific `0x01`) occur in gameplay/control lanes rather than the named startup initializer path. Practical classification is now `RP-16 not proven startup/default weapon` while still remaining a real executable-backed row with unresolved non-startup acquisition role.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` input-side closure pass for the hidden/debug JL-9 trigger path. The active database now carries conservative input-map helper promotions (`0x80042ec4` -> `psx_input_map_install_profile`, `0x8002abb8` -> `psx_input_map_set_code_to_padmask`, `0x8002abe0` -> `psx_input_map_update_state_for_pad`, `0x8002adbc` -> `psx_input_map_get_code_and_edge`) plus new decompiler comments at `0x80042ec4`, `0x8002abe0`, and `0x80012c30`. Current best read is now explicit and executable-backed: `psx_object_update_runtime_input_modes` gates debug grant on `local_14 == 0x1e`; input code `0x1e` resolves through an exact-match 0x32-entry map; and that code maps to mask `0x2800` in every recovered profile branch, corresponding to the practical chord `R1 + Circle` under the active digital pad bit layout.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` section0-slot closure pass for JL-9 legit-path testing. Current best read is now decisively tighter: `psx_section0_dispatch_root_seed_marker_channel_table` seeds slot `0x14` as `(kind=3, channel=0x0d)`, `psx_section0_dispatch_root_apply_packed_channel_actions` dispatches kind `3` into `psx_weapon_channel_unlock_and_seed_markers(channel)`, and shipped PSX scene-cache root records contain authored bytes with `(byte & 0x3f)==0x14` across 22 maps (119 hits / 3298 scanned root records). Practical consequence is that non-debug shipped data can in fact drive the `unlock(0x0d)` lane; remaining uncertainty is route/timing reachability in ordinary play, not byte-path existence.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 live `SLUS_002.68` passcode decode closure pass centered on `psx_passcode_screen_eval_current_entry` (`0x80034e38`), `psx_passcode_decode_to_mission_selector` (`0x8003ec8c`), and table block `0x80064bbc/0x80064bd0/0x80064be4/0x80064bf8`. The pass closes the transform math (`entry[1..3]-0x1b` triplet match + first-char delta lane), recovers concrete table bytes from memory, and confirms special index behavior: `0x10` sets `psx_hidden_passcode_flag`, `0x0f` clears `psx_level_runtime_header_state`, and `0x11` returns sentinel `0x12`, with all three bypassing first-char validation. Current strongest string closure is hidden/debug `?0SR` (canonical `L0SR` when selector is `0`), plus `?RTN` and `?QQQ` sibling specials. Practical JL-9 consequence remains a staged hidden/debug gate path (`hidden flag` + `input code 0x1e` + `psx_debug_extra_channel_gate`) rather than an ordinary direct unlock lane.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` final-enable-sequence closure pass for the JL-9 hidden/debug lane at `0x8002ba9c`, `0x800232f0`, `0x8002fd90`, and `0x8002fff4`. Current best read is now explicit in executable terms: final `0x0d` unlock is gated by `psx_debug_extra_channel_gate` read at `0x8002fff4`; that gate is written only under `psx_hidden_passcode_flag==0 && psx_level_runtime_header_state==3` at `0x800232f0`; grant entry itself requires `psx_hidden_passcode_flag!=0` plus input code `0x1e` at `0x80013174`; and the strongest practical outcome is a two-phase hidden flow rather than a guaranteed one-action trigger.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` RP-16 classification pass around selected-id `0x01` with direct row/acquisition/HUD evidence. Current best read is tighter in four concrete ways: row `0x01` (`0x80064690`) is real structured weapon-definition data (`RP-16`) rather than invalid filler; primary shop direct-unlock progression still runs through `03..0c` and does not include `0x01`; the observed shop-side `0x01` table entry sits in the secondary `0x0a..0x0e` ammo-top-up branch (`0x8002e32c`) rather than primary unlock helper `0x8002e5f0`; and HUD short-label lookup for selected weapon id remains table-driven through `FUN_800455d4` (`0x80064e90` / `0x80064e9c`) with row-name identity still anchored by inline row bytes. Practical classification is now "real early row with legacy/startup/placeholder-like behavior" rather than "invalid/unused slot", pending one concrete non-debug acquisition writer.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` legitimate-acquisition verdict pass using emulator-grounded selected-id mapping (`0x8014577e`, `0x0c=JL-2`, `0x0d=JL-9`) as hard anchor. Current best read is now explicit by lane: normal loadout and shop direct unlock paths remain capped at `<=0x0c`; hidden/debug input gate still leads to the only recovered fixed-immediate `unlock(0x0d)` site (`0x80030004`); scripted packed-action dispatch remains the only plausible non-debug `0x0d` exception but is still authored-data dependent and unproven in shipped section0 content; practical classification therefore remains `JL-9 strongest hidden/debug-conditioned` rather than presently proven normal-flow unlock.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` broad acquisition-systems pass over default loadout/init, mission transition mode apply, pickups, shops, scripted packed awards, and debug grant paths for ids `0x0d` (`JL-9`) and `0x01` (`RP-16?`). Current best split is now explicit and source-backed: ordinary loadout and shop front path remain `<=0x0c` with no direct `0x0d`; debug grant still conditionally unlocks `0x0d` behind `psx_debug_extra_channel_gate`; scripted/pickup data-driven paths can still produce either id when authored; and `0x01` is not excluded from normal content because the shop channel map contains `0x01` in the alternate branch and low-id action commit paths also accept it.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now records a 2026-04-11 live `SLUS_002.68` reconciliation pass that incorporates the user-verified selected-weapon byte map at `0x8014577e` (`00..0d`) and tightens the old local-id-versus-row-id ambiguity. Current best model is explicitly two-domain: callers feed compact channel/local codes into `psx_apply_channel_effect_and_commit_selected_item_id` (`0x8002ef34`), then `0x8002f15c` resolves through `channel_commit_row_selected_item_id[(channel*10)+9]` into committed row-id domain (`00..0d`) before `0x8002f168` stores to the nested runtime `+0x1c` field family that includes the selected byte at `0x8014577e`. This corrects earlier shorthand that could imply direct caller-code equality with committed selected row id.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a focused 2026-04-11 live `SLUS_002.68` gate-closure pass for the exact JL-9 visibility preconditions requested around `psx_debug_extra_channel_gate`, `psx_hidden_passcode_flag`, and the selected-weapon watch at `0x8014577e`. Current best read is tighter in four practical ways: `DAT_8006739d` remains one-writer/one-reader (`0x800232f0` write, `0x8002fff4` read); `DAT_80067454` writer/reader roles are now bounded across decode/arm/reset/input handlers; unlock-capable channel-`0x0d` entry points are explicitly separated into debug bulk grant versus scripted packed-action dispatch; and normal-lane evidence still clusters at `<=0x0c` while non-debug shipped `0x0d` authoring remains unproven. The pass also landed one conservative live rename (`0x8002ba9c` -> `psx_hidden_passcode_arm_runtime_state`) plus durability comments at `0x8002bab8`, `0x80013154`, `0x8002fff4`, and `0x8002f170`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now captures the decisive starter-only RAM compare against the earlier all-weapons dump and retracts the previous `0x1456fc` inventory-list hypothesis. The compact `0x1456fc..0x145748` `0x0002..0x000b` sequence is unchanged across both dumps and is therefore not the live owned-weapon list; the dynamic lane starts at `0x14574c` and the strongest current field closure is now byte `0x14577e` (`0x0c` in the all-weapons dump versus `0x02` in the starter-only dump), which executable-side passes tie to selected/committed weapon row-id state inside the nested runtime `+0x1c` field family. The compare also keeps `0x67944` interesting as a changing watch (`0x0000000b` versus `0x00000001`) but downgrades it to unproven because no direct static xrefs were recovered for `0x80067944`. Current best read is now channel-state ownership plus dynamic runtime current-weapon fields, not a contiguous owned-id inventory array.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now captures a focused live `SLUS_002.68` ownership-model pass tied to the observed dump split (`0x801456fc..0x80145748` static, `0x8014574c..0x801457d0` changed). Current best read is tighter in four concrete ways: ownership query is channel-state based (`psx_marker_channel_mode_is_enabled` reads `psx_marker_channel_runtime_block[(channel*4)+0x34]`), per-channel ammo/step state is `+0x6c` (via `psx_marker_channel_get_mode_step_value` and channel add/unlock helpers), active selected channel is byte `+0x32`, and commit-to-weapon resolution remains table-driven (`channel_commit_row_table[(channel*10)+9]` at `0x8002f15c` into `idx*0x26` row access at `0x800315d8`) rather than a direct contiguous owned-id list. The live database also fixes a symbol collision by renaming `0x8002fd90` to `psx_debug_grant_weapon_channels_and_ammo`, preserving explicit post-gate extra unlock semantics at `0x8002fff4 -> channel 0x0d`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now adds a focused live `SLUS_002.68` runtime-ownership pass on RAM block `0x8014574c..0x801457d0` and closes the strongest current interpretation for watched byte `0x8014577e` (`file offset 0x14577e`). Current best read is now tighter in four concrete ways: `0x14577e` sits inside the nested player-runtime `+0x1c` field family reached through `DAT_800789f8 -> +0x8 -> +0x18 -> +0x1c`; that field family is written from `channel_commit_row_selected_item_id` in `psx_apply_channel_effect_and_commit_selected_item_id` and reset in `psx_set_debug_extra_channel_gate`; the surrounding block shape matches heap-resident per-object runtime state initialized by `psx_object_create_simple_record`; and the observed byte delta `0x0c` (all-weapons) versus `0x02` (starter-only) is therefore strongest as selected weapon row-id metadata, not primary ownership/ammo/HUD-cache storage.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now adds a focused live `SLUS_002.68` ownership/xref pass on the user-tracked block `0x80067938..0x80067958`, with direct closure on the `0x80067944` ambiguity. Current best read is now tighter in four concrete ways: `0x80067944` has no recovered static xrefs in this image under word/halfword/byte probes and is therefore not currently supported as selected-weapon-id or selected-row-id storage; `0x80067938` is reaffirmed as `psx_ctor_placement_section_ptr` installed by `wdl_resource_bundle_load_by_index` and consumed by `psx_apply_deferred_control_command` as constructor/deferred-control section state; nearby `0x8006793c/0x80067940/0x80067948` are input-mode dispatch state in `psx_object_update_runtime_input_modes`; and `0x80067954/0x80067958` are present/spec-upload draw-env state with one conservative helper promotion (`FUN_800461d0` -> `psx_draw_progress_overlay_and_swap_drawenv`).
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now closes the compact local-id versus weapon-row-id question for live `SLUS_002.68` as a table-driven conversion, not a standalone arithmetic helper. The new strongest chain is `caller channel/local code -> psx_apply_channel_effect_and_commit_selected_item_id(0x8002ef34) -> channel_commit_row_selected_item_id[(channel*10)+9] load at 0x8002f15c -> committed_selected_item_id -> idx*0x26 resolver at 0x800315d8 from base 0x8006466a`, with concrete JL mapping `local/channel 0x0b -> row 0x0c (0x80064832, JL-2)` and `local/channel 0x0c -> row 0x0d (0x80064858, JL-9)`. Three durable live comments were added at `0x8002f15c`, `0x800315d8`, and `0x8001d3fc` to preserve this conversion model for future RAM-dump reconciliation passes.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now closes the user-reported pointer delta near selected-weapon state watches (`0x80078248` in all-weapons versus `0x800782b8` in starter-only) as a render double-buffer artifact, not a weapon/inventory struct discriminator. Live `SLUS_002.68` decompiles now show `psx_platform_init` seeding both draw-env records, paired disp-env records at `+0x5c`, and `DAT_80067954` flipping between them in `psx_present_frame_and_flip`, `render_reset_draw_state`, and `FUN_800461d0`. Current practical consequence is tighter scope: this nearby pointer lane should no longer be treated as selected-weapon ownership state, and weapon identity work should stay on the channel-commit and weapon-row resolver path.
Latest verified batch: the earlier `0x1456fc` / `0x145744` / `0x67944` patch-candidate story is now superseded by the starter-only compare and should be treated as withdrawn. `0x1456fc..0x145748` is static across both dumps, so it is not the live owned-weapon list; byte `0x14577e` is now the strongest executable-backed dynamic selected-weapon row-id field, while `0x67944` remains only a changing watch without direct static-xref closure.
Latest verified batch: the earlier watched-value shorthand `JL-?=11` has now been superseded by direct emulator verification of the selected-weapon byte at `0x8014577e`: `0x0c = JL-2`, `0x0d = JL-9`, with the full `00..0d` row-id map now captured in [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md). This removes the older inference step for JL-2 versus JL-9 identity and shifts the remaining work entirely onto legitimate-acquisition proof and RP-16 classification.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now captures the current blocker much more precisely after another multi-lane `SLUS_002.68` pass. The strongest RAM-side lead has moved decisively to dense small-byte runtime clusters around file offsets `0x133000`, `0x133416`, and `0x1335d4`, with a weaker secondary candidate around `0x422c..0x4440`; by contrast, the earlier `DAT_80064355[(channel*10)+9]` field is now explicitly documented as not acting like a plain final JL row id in the sampled dump. On the code side, `0x8001E37C` is now promoted to `psx_handle_special_input_code`, which is the strongest current upstream helper for the hidden `0x1e` trigger range, while the ammo-side helper family is now tighter through newly named marker/runtime helpers. The remaining blocker is now singular and concrete: one executable-side inventory/HUD anchor is still needed to prove which of the current RAM candidate regions is the real live weapon-slot table.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now captures a third targeted six-agent `SLUS_002.68` cycle that sharpened both the RAM and input-code stories without yet fully closing them. The main negative result is now explicit: the candidate commit-table byte at `DAT_80064355[(channel*10)+9]` does not show plain `0x0c/0x0d` values in the sampled main-RAM dump rows, so that exact field is not behaving like a direct final JL row id in this capture. The strongest new positive lead is elsewhere in the same dump: denser table-like `0x0c/0x0d` clusters now stand out around file offsets `0x133000`, `0x133416`, and `0x1335d4`, which are better candidates for real live inventory/slot state and should be the next RAM target. The same pass also promoted `0x8001E37C` to `psx_handle_special_input_code`, tightening the upstream side of the hidden-passcode trigger path even though the exact `0x1e` -> button-chord mapping still remains open.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now captures another six-agent `SLUS_002.68` pass focused on the JL-2 ammo lane, the JL-9 debug trigger path, the 2 MiB main RAM dump, and direct inventory/HUD mapping helpers. The strongest new clarification is on the trigger story: executable evidence now closes `hidden passcode active -> gated input code 0x1e -> psx_debug_grant_weapon_channels_and_ammo -> extra 0x0d unlock behind psx_debug_extra_channel_gate`, but still does not prove that input code `0x1e` specifically means `R1 + Circle`. The same pass also tightened the RAM-dump read without rejecting it: `binary/Crusader - No Remorse Weapons Main Ram.bin` is still plausible main RAM and now has candidate compact slot-like records near file offsets `0xA0/0xB0/0xC0`, but the dump still needs one confirmed executable-side inventory/HUD anchor before it can decode the runtime weapon list. Live database cleanup in the same pass also added `0x800232f0` -> `psx_set_debug_extra_channel_gate` and reinforced the selected/equipped chain around `0x8002ef34`, `0x8002f15c`, `0x800315d8`, `0x8003d02c`, and `0x800424ac`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now records a fresh six-agent `SLUS_002.68` pass that added a second runtime-artifact lane instead of only rechecking the executable. The new distinction is now explicit: `binary/Crusader - No Remorse Memdump Weapons.bin` remains a VRAM/HUD artifact, while `binary/Crusader - No Remorse Weapons Main Ram.bin` is plausible 2 MiB PSX main RAM but still not self-decoding enough to resolve the selected `JL-?` slot as `JL-2` versus `JL-9` without the executable-side id/name chain. The same pass also tightened the live database around the selected/equipped path with fresh comments on `0x8002ef34`, `0x8002f15c`, `0x800315d8`, `0x8003d02c`, `0x800424ac`, and explicit extra-unlock naming at `0x80030004` -> `psx_weapon_channel_unlock_and_seed_markers`, while keeping `JL-2 AMMO` as the strongest current normal-lane lead.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) has now been normalized after a new six-agent `SLUS_002.68` pass instead of continuing to accumulate repeated follow-up narratives. The same batch adds two concrete deltas: the checked `binary/Crusader - No Remorse Memdump Weapons.bin` artifact now has a narrower runtime-artifact read as PSX VRAM with HUD/icon-atlas content but no direct slot-RAM proof for `JL-2` versus `JL-9`, and the next weapon-side unknown is now better framed as `JL-2` ammo/storage behavior because `JL-2 AMMO` exists at `0x800642b6` while `JL-9` still remains the stronger extra hidden/debug-conditioned lane. The live PSX database also gained a few more small durable artifacts in this pass, including the explicit helper name `psx_weapon_channel_unlock_and_seed_markers` at `0x80030004` plus confirming HUD/commit comments around `0x8002f15c`, `0x800323b0`, `0x80042690`, and the weapon-def base `0x8006466a`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now records a narrow 2026-04-11 follow-up on live `SLUS_002.68` that split symbolic cleanup from runtime-artifact checking. The live PSX database now carries evidence-backed JL-lane data names (`psx_hidden_passcode_flag`, `psx_debug_extra_channel_gate`, `psx_level_runtime_header_state`, `channel_commit_row_table`, `channel_commit_row_selected_item_id`, `committed_selected_item_id`, `psx_weapon_spawn_type`, `psx_weapon_spawn_audio_event_id`, and `psx_weapon_spawn_state_selector`), while the checked `binary/Crusader - No Remorse Memdump Weapons.bin` artifact is now explicitly reclassified as a full 1 MiB PSX VRAM dump rather than slot/inventory RAM. The same pass also sharpens the next pivot if the extra hidden/debug lane remains `JL-9`: `JL-2` now has direct `JL-2 AMMO` string evidence (`0x800642b6`) without a matching plain `JL-9 AMMO` peer, so the next useful unknown becomes JL-2's ammo/storage and normal-acquisition behavior rather than more blind JL-9 existence proof.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 MCP-only direct mapping proof pass for the requested chain on live `SLUS_002.68`: channel commit (`0x8002ef34`) -> committed item id load from `DAT_80064355[(channel*10)+9]` at `0x8002f15c` -> row-indexed weapon-def access with explicit `idx*0x26` stride at `0x800315d8/0x8003160c` from base `0x8006466a` -> concrete row split `0x0c=0x80064832 (JL-2)` vs `0x0d=0x80064858 (JL-9)`. The note now also includes row-byte diff evidence from MCP compare (`+0x24: 0x4b -> 0x0f`, name-field byte `0x32 -> 0x39`) and records that `0x800322b4..0x800325f8` remains only supporting UI-tail evidence (`0x0121 -> 0x013f`), not the primary commit/resolver path.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 MCP-only row-field consumer trace for `JL-2` (`0x80064832`) versus `JL-9` (`0x80064858`) on live `SLUS_002.68`. The new closure is lane-level: shared `+0x1c=0x18` still converges through `psx_weapon_def_apply_spawn_profile_by_index` (`0x8003d02c`) into the same type-indexed active art-header bind path in constructors (`0x800249f4` / `0x80024eec` via `DAT_800758d8`), while diverged `+0x24` (`0x4b` vs `0x0f`) remains consumed as a compact transition/state selector (returned from `0x8003d02c`, then used in callers like `0x80018bc4/0x80018bf8`) rather than a direct base-resource pointer. Current best answer is shared base-art lane with distinct selector/state lane, and runtime capture is still required only for exact final frame/resource-token closure.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 MCP-only four-lane acquisition comparison on live `SLUS_002.68` across normal loadout (`0x8002f814`), shop (`0x8003de68`), scripted grant (`0x800311f4..0x800313b4`), and debug grant (`0x8002fd90`). The strongest new discriminator is address-local and explicit: only debug grant performs the post-`0x0c` extra unlock call (`0x80030004 -> psx_weapon_channel_unlock_and_seed_markers(0x0d)`) behind `DAT_8006739d` read at `0x8002fff4`, while the L0SR-linked gate chain remains `0x8003ed28 (DAT_80067454=1)` -> `0x80013154/0x80013174` -> `0x8002fd90`. Current best conclusion remains that the extra post-L0SR non-PC weapon lane is `JL-9` (`0x80064858`, index `0x0d`) rather than `JL-2` (`0x80064832`, index `0x0c`), with three conservative new comments at `0x8003de68`, `0x80031344`, and `0x80030004`.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 MCP-only recheck of the hidden passcode to extra-unlock chain on live `SLUS_002.68`. The practical read is tighter: hidden decode branch at `0x8003ed28` sets `DAT_80067454`, runtime input gate at `0x80013154/0x80013174` reaches `psx_debug_grant_weapon_channels_and_ammo`, and the extra late unlock remains a second gate (`DAT_8006739d` at `0x8002fff4`) for index `0x0d`. Current direct conclusion remains that the extra late unlock lane maps to `JL-9` (index `0x0d`) rather than `JL-2` (`0x0c`), with the remaining closure target narrowed to one direct UI label-resolver chain plus full `DAT_8006739d` writer-context classification.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 MCP-only pass that tightened the unresolved `0x800323xx` lane and selected-id tail evidence in live `SLUS_002.68`. The lane is now anchored as render-mode UI state via `psx_render_mode_dispatch` (`0x800350a8`) and callback table `0x800648b4`, with `psx_ui_state_handler_322b4_color_cycle` driving `DAT_800678f8` from `0x80064880` and a terminal toggle `0x0121 -> 0x013f`, then consumed by `psx_hud_draw_selected_item_tile_bar` at `0x800424ac`. This batch also landed conservative naming/comments (`0x8003ddcc`, `0x800424ac`, and comments at `0x800323b0`, `0x800323e4`, `0x80042690`) and keeps the strongest current JL read as selected-id-backed (`JL-2 ~ 0x0121`, `JL-9 ~ 0x013f`) while direct channel-number equivalence remains a dedicated follow-up target.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a 2026-04-11 shipped-map weighting follow-up that keeps executable-side evidence authoritative and uses scene-cache evidence as support. Current best read tightens one practical decision: `JL-2` remains the stronger normal-lane shipped candidate (`0x0c`), while `JL-9` remains the stronger exceptional/debug-conditioned lane (`0x0d` behind `DAT_8006739d`). The pass also leaves concrete continuation anchors: current `DAT_8006739d` writer condition at `0x800232f0`, late-mode candidate range (`46..58` and `63`) from `0x80063e68`, and dense section0 cache targets (`map 46/49/63`) for direct placement correlation.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now records a focused live `SLUS_002.68` row-to-consumer pass that resolves the earlier JL-9 selector ambiguity. Current best read is tighter in three concrete ways: the candidate pair is corrected to `JL-2(+0x24=0x4b)` versus `JL-9(+0x24=0x0f)` while both keep `+0x23=0x0e`; both rows still converge through shared `+0x1c=0x18` into the same type-indexed base art bind path (`psx_weapon_def_apply_spawn_profile_by_index` -> object constructors -> `psx_type_art_active_header_bank[type]`); and the `+0x24` consumer lane is now evidenced as transition/spawn-state-facing rather than direct bundle-id-facing. Remaining closure stays narrow: runtime capture is still required to export final JL-9 frame/resource identity with certainty.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now adds a direct UI/state disambiguation pass for the hidden-passcode "all weapons received" outcome on live `SLUS_002.68`. The previously suspicious `0x800323xx` lane is now recovered as render-mode UI color/flash handling (`psx_render_mode_dispatch` callback table at `0x800648b4`, `0x80064880` halfword ramp into `DAT_800678f8`, consumed by `FUN_800424ac` tile-color writes), not weapon-name display/selection logic. The strongest identity proof now sits in the commit path: `psx_apply_channel_effect_and_commit_selected_item_id` commits `DAT_80064355[channel*10]`, channel `0x0d` row at `0x800643ce` stores item id `0x0d`, and weapon-def index `0x0d` is row `0x80064858` (`JL-9`) versus index `0x0c` row `0x80064832` (`JL-2`).
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now includes a narrow 2026-04-11 reconfirmation pass over hidden passcode decode and late weapon unlock gating in live `SLUS_002.68`. The new evidence closes two practical points: `DAT_8006739d` usage in this image is now exhaustively bounded to one writer (`0x800232c0`) and one reader (`0x8002fd90`), and the extra conditional unlock call in `psx_debug_grant_weapon_channels_and_ammo` is now tied to index `0x0d` as the post-`0x0c` final JL lane, strengthening the JL-9-over-JL-2 conclusion for the hidden/debug route while keeping one explicit caveat that a single direct UI label resolver chain was not recovered in this pass. This batch also landed small durable artifacts in the live database: `FUN_8003ddcc` -> `psx_shop_entry_get_mode_gated_cost_by_slot`, new thin wrappers `psx_script_dispatch_packed_channel_actions_from_object` (`0x800203b0`) and `psx_script_dispatch_marker_mode_action_from_byte` (`0x800203ec`), and targeted decompiler comments at `0x8003de68`, `0x8002fff4`, and `0x80064858` to preserve the `0x0c` (`JL-2`) versus `0x0d` (`JL-9`) acquisition split.
Latest verified batch: [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now consolidates the focused live `SLUS_002.68` JL-9 / hidden-passcode / debug-leftover sweep instead of leaving those results spread across subagent-only Ghidra edits. The live PSX database now has a tighter weapon-side helper family (`psx_weapon_marker_activate_by_index`, `psx_weapon_marker_add_with_cap_by_index`, `psx_weapon_def_get_u16_with_mode_gate`, `psx_weapon_def_apply_spawn_profile_by_index`, `psx_weapon_shop_try_apply_entry`, `psx_debug_grant_weapon_channels_and_ammo`, and the passcode decoder/generator pair), and the practical read is sharper in four ways: `JL-9` is now closed as a real final indexed PSX weapon-definition row rather than a stray string; the hidden passcode decoder is now directly recovered as transformed table logic instead of only inferred from folklore; the strongest current `how do you get JL-9` answer is a hidden passcode branch that sets `DAT_80067454` and then reaches the bulk weapon-unlock helper's extra late channel; and the remaining PSX gaps are now narrower again, concentrated in exact `channel -> JL-9` proof, exact sprite/frame/resource mapping, and exact level-placement correlation rather than in the broader existence question.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now capture the next six-agent PSX table-typing pass rather than another broad sweep. The live database now promotes `DAT_80067938` to `psx_ctor_placement_section_ptr`, `DAT_800676d8` to `level_clut_table_ptr`, `DAT_80067840` to `psx_control_opcode_stream_table`, and `DAT_80063e54/0x80063e68` to paired per-level selector/channel tables, while the earlier `DAT_800675ec` marker/control runtime block now has a stronger local field map centered on `+0x34`, `+0x6c`, and `+0x88/+0x8c`. Current best read is tighter in three practical ways: the constructor-placement section is now clearly a section-pack root with subordinate indexed control rows rather than one opaque blob; the CLUT and opcode-stream tables are now pushed off the shortlist of direct `0x0042` art discriminators and into palette/control support; and the real remaining structural blocker family is now the level-indexed `0x63e54/0x63e68/0x675ec` control island plus the exact installed rows fed from the decompressed/runtime-bank install chain.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a storage-mapping pass on live `SLUS_002.68` that stayed off broad draw-path analysis and traced ownership from WDL section installs through compressed-state unpack into runtime banks consumed by type `0x0042` object creation/presentation. The live database now also carries four small evidence-backed names in this lane (`FUN_8003b00c` -> `psx_lzss_unpack_into_level_buffer`, `FUN_8003aba8` -> `psx_lzss_pack_level_buffer`, `DAT_8006b5d8` -> `psx_level_state_compressed_blob`, `DAT_8006763c` -> `psx_level_heap_cursor`) plus targeted comments at `0x80039af0` and `0x800249f4`. Current best read is tighter in one practical way: the map-storage ownership chain is now concretely anchored from level-load section pointers to constructor/draw-time bank consumers, and the remaining blockers are narrowed to unresolved subordinate table schemas and per-item map-104 `0x0042` resource/state correlation.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now captures a focused PSX type-policy provenance pass around `DAT_800675f8` / `psx_type_policy_table_ptr` for `map 104` type `0x0042` and control type `0x0066`. The live `SLUS_002.68` database now adds one safe reader rename (`0x8001408c` -> `psx_update_nearest_policy80_contact_marker`) plus targeted comments at `0x800398f0`, `0x8002bf0c`, `0x80041604`, and `0x800140c8` that pin installation and major consumer roles. Current best read is tighter in three practical ways: pointer provenance is now explicit as level-load section-pack storage (single writer at `0x800398f0` in `wdl_resource_bundle_load_by_index`), consumer coverage now extends beyond the older `0x1000/0x0600/0x2000` set to additional policy bits (`0x0008`, `0x0010`, `0x0020`, `0x0080`, `0x0100`, `0x0800`, `0x4000`, `0x8000`), and the fixed map-104 sample rows (`25/30/31/35/85/86` and control `53`) still show `runtimeDiagnostic.typePolicy.word=null`, confirming concrete row-word capture remains a runtime export gap rather than an executable-static decode gap.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now captures a focused object-local route-bit provenance pass for fixed `map 104` `0x0042` sample items `25/30/31/35/85/86` at the decisive `obj+0x1c & 0x0400` branch in `psx_object_integrate_motion_and_route_visible`. Current best read is now stricter and evidence-backed: `psx_object_create_simple_record` (`0x80024b48`) and `psx_object_create_compound_record` (`0x80025040`) remain the strongest object-local writers because they copy authored `u5` directly into `obj+0x1c`; inspected named mutators in the same lane (`psx_type42_transition_selector_tick`, `psx_object_select_state_from_transition_table`, `psx_object_advance_state_script`, `psx_apply_deferred_control_to_live_objects`, `psx_object_handle_control_pair_0a`) change other route/control bits but did not reveal a direct object-local `0x0400` set/clear transform; and recovered `0x0400` writes remain nested/global (`psx_object_state_machine_dispatch_tick` nested runtime write at `0x8001a078`, global policy write in `psx_object_handle_control_pair_0a` at `0x80022a14`). The remaining gap stays narrow: a live capture is still required to close object-local `0x0400` provenance for this sample pack.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a focused frame-geometry pass on active `SLUS_002.68` following the selector/latch bridge into exact width/height/origin consumers. The live evidence chain is now explicit from `psx_object_select_state_script` (`obj+0x9e` install) and `psx_object_advance_state_script` (`obj+0x94` latch) into `psx_project_object_main_visible` / `psx_project_object_special_visible_queue`, then into `psx_resource_frame_origin_x/y` and `psx_resource_frame_width/height`. Current best read tightened one step further: for the fixed `map 104` `0x0042` sample pack, `64x40` versus `64x64` is most strongly a live frame-token (`obj+0x94`) outcome within a shared type resource lane, not a late presentation modifier; the remaining closure task is a compact live sample of bound-resource kind plus latched frame token for items `25/30/31/35/85/86`.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now captures a broad but concrete table-inventory pass against live `SLUS_002.68` focused on unresolved `map 104` / type `0x0042` storage and rendering coverage. The live database now adds six marker-channel/runtime-state helper names (`0x8002f190`, `0x8002f250`, `0x80031840`, `0x8003185c`, `0x80031878`, `0x80031a3c`) and targeted table comments on `0x80063e68`, `0x800675ec`, and `0x80063e54`. Current best read is tighter in two practical ways: the known descriptor/transition/runtime-bank tables are reaffirmed as shared/generic infrastructure rather than a missing unique `0x0042` fork, and the `0x80063e54/0x80063e68/0x800675ec` level-indexed control island is now promoted as a high-value unresolved table family worth explicit struct/table mapping in the next batch.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now capture another six-agent concrete `map 104` `0x0042` pass over the same fixed sample pack instead of broadening the search surface. The live PSX database now also names two selector-overlap probes, the runtime snapshot/release helpers, and a wider root marker/channel helper family. Current best read is tighter in three practical ways: the `64x64` versus `64x40` split still looks more like shared-resource / different-frame-state behavior than a different constructor bind path; the selector-to-frame bridge is now explicit through `obj+0x9e` install, `obj+0x94` latch, and the later frame-geometry consumers; and the stage-1 versus stage-2 branch point is now explicit in `psx_object_integrate_motion_and_route_visible`, even though the exact live `0x0400` provenance for this sample pack still needs capture. The next `map 104` pass should therefore stay on items `25/30/31/35/85/86` and capture bound resource identity plus live frame/state and route-bit state at the decisive branch instead of widening heuristics.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now capture the first concrete `map 104` `0x0042` `runtimeDiagnostic` follow-up against exported scene items rather than another generic six-track theory pass. The live PSX database now names the spawn-side selector bridge (`psx_transition_spawn_and_seed_selector_from_record`), a root-family decoder helper (`psx_section0_dispatch_root_find_marker_record_by_channel`), two nearby resource-upload helpers, the type-policy pointer (`psx_type_policy_table_ptr`), and the transition row tables (`psx_type_transition_mode_policy_rows`, `psx_type_transition_selector_rows`). Current best read is tighter in four practical ways: the root and constructor section-0 families now have explicit named entry points but still converge through the same shared `0x0042` descriptor row; constructors are now proven to seed `obj+0x1c` by directly copying the authored lane word, so exported `initialWord` values are meaningful authored state; the strongest recovered `0x0400` control is still nested-state-side rather than a direct object-local `0x0042` writer; and `DAT_800675f8` is now better modeled as a level-loaded per-type policy pointer, not a per-lane discriminator. The next `map 104` pass should therefore sample bound-resource identity plus live frame/state against the fixed item set (`item:25/30/31/35/85/86`) rather than widening donor logic or revisiting descriptor speculation.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [map_renderer/src/lib/psx-cache.js](k:/ghidra/crusader_map_viewer/map_renderer/src/lib/psx-cache.js) now capture both sides of the next PSX narrowing step. The live Ghidra work stayed split across six non-overlapping `0x0042` tracks, and the viewer exporter now serializes the resulting channel model as a per-item `runtimeDiagnostic` payload in scene version `psx-runtime-record-probe-v10`. Current best read is now operationally tighter: the next `map 104` pass no longer needs to rediscover which fields matter, only to populate the exported channels for representative `0x0042` items with live evidence for pre-latch selector dispatch, latched state, object-local route flags, nested runtime state, resource kind, and the live type-policy word.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [docs/psx/psx.md](docs/psx/psx.md) now capture another six-track live `SLUS_002.68` PSX batch with deliberately separate coverage areas instead of overlapping helper sweeps. Current best read is tighter in four ways: the per-type art/cache pair is now named by behavior (`DAT_800758d8 = psx_type_art_active_header_bank`, `DAT_800758c8 = psx_type_art_built_resource_bank`); `psx_type42_transition_selector_tick` now has an early eligibility gate through `psx_object_is_within_view_margin` before it can emit selector `3/4` ahead of the later latch copy; the root-dispatch-side `0x0042` mode gate still looks control-only rather than a distinct map-facing presentation family; and the anonymous control-island recovery now proves `0x0400` writes in the wider nested runtime state machine and related policy controls without yet pinning a direct object-local `obj+0x1c |= 0x0400` writer. The next `0x0042` discriminator is therefore narrower again: a concrete runtime sample now needs to separate pre-latch selector dispatch, latched `obj+0x94`, object-local `obj+0x1c`, nested runtime state words, and bound resource kind instead of treating them as one flat route/state bucket.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [docs/psx/psx.md](docs/psx/psx.md) now capture the next focused live `SLUS_002.68` follow-up on unresolved `0x0042` rather than another broad render pass. Current best read is tighter in five ways: type `0x0042` is now pinned to exact descriptor-table slot `0x80063220 -> 0x800626f8`; both section-0 constructor placements and root-dispatch rows still enter that same generic create/update/release family; type `0x0042` now also has a dedicated transition helper `psx_type42_transition_selector_tick` that can dispatch low turning selectors `3/4` before the `+0x94`-style runtime latch copy; `obj+0x1c` writer behavior is clearer because constructors seed it from authored `u5` while `psx_object_select_state_from_transition_table` only mutates bit `0x0002`; and `DAT_800675f8` now reads as policy bits (`0x1000` nearby-publication suppression, `0x0600` stage-1 ordering class, `0x2000` semitrans policy) rather than as the missing main route discriminator. The remaining next-step discriminator is therefore narrower again: concrete `0x0042` samples need to correlate pre-latch selector dispatch, latched `obj+0x94`, `obj+0x1c` bit `0x0400`, and bound resource kind, not just raw `u5` or generic transition-row contents.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [docs/psx/psx.md](docs/psx/psx.md) now capture one more six-track live `SLUS_002.68` PSX pass centered on unresolved `0x0042` routing. Current best read is narrower in four practical ways: constructor and root-dispatch records both hand authored flags into `obj+0x1c`; `0x0020` is still the broad world-visible gate, `0x0002` still reads as orientation/extents behavior rather than a lane split, and `0x0400` is now the strongest recovered stage-2 special-visible selector; `psx_object_select_state_from_transition_table` now gives a concrete per-type selector source ahead of `psx_object_select_state_script`; `psx_load_type_state_banks` now reads as the `DAT_800758cc/d0/d4` installer while `psx_stream_install_type_runtime_banks` is the packed-stream helper that can also install `DAT_800758d8`; and `DAT_80067794` is now tighter as save/transition runtime-header state via `psx_snapshot_level_runtime_header_block` / `psx_apply_level_runtime_header_block`, not as the missing `0x0042` art-binding source. The next executable-backed discriminator is therefore more specific again: sample representative `map 104` `0x0042` objects through `obj+0x1c`, `obj+0x10`, `obj+0x9e`, and `obj+0x94`, plus the `DAT_80063b4c` transition-table row for type `0x0042`, instead of spending another batch on raw `u5` or descriptor-table speculation.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now consolidate the full six-track live `SLUS_002.68` PSX sweep instead of leaving the result fragmented across individual helper passes. Current best read is narrower in a way that changes the next execution order: unresolved `0x0042` is no longer plausibly blocked on a missing unique per-type descriptor fork or on HUD/overlay presentation behavior. `0x0042` still shares the generic `0x003e..0x0050` descriptor cluster, constructor-placement `0x0042` still reads as a compound -> advance-state -> main-visible route inside that generic family, both world-facing lanes choose sprite versus image-table submitters from the bound resource header kind, and the HUD/overlay lane remains an explicit non-map-facing exception that can branch from overlay-slot policy instead of normal world-object kind checks. The next practical discriminator for viewer recovery is therefore representative runtime-bank/state/resource-kind evidence for `map 104` `0x0042` families, especially the split between constructor-placement `u5=0x0020` and root-dispatch `u5=0x0030/0x0022`, not more broad donor reuse or descriptor-table speculation.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a focused live `SLUS_002.68` world-frame/render-wrapper cleanup around `0x80031f0c`, `0x80031f9c`, `0x800320bc`, `0x80039dc4`, `0x8003977c`, and `0x800391f0`. The live PSX database now names `0x80031e0c` as `psx_lset_session_loop`, `0x80031f0c` as `psx_lset_world_frame_wrapper`, `0x80031f6c` as `psx_lset_session_teardown`, `0x800350a8` as `psx_render_mode_dispatch`, `0x80039ef4` as `psx_level_post_load_runtime_reset`, and `0x80044104` as `psx_present_frame_and_flip`, with direct technical comments set at the same loader/bank/runtime-header anchors. Current best read is tighter for reconstruction work: this lane now provides a clean evidence-backed chain from map-index bundle load and per-type bank install through runtime-header application to per-frame world visible draw and display flip, reducing ambiguity between storage decode and final presentation wrappers.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a focused live `SLUS_002.68` main-visible ordering/update cleanup around `0x8001263c`, `0x80012c30`, `0x8002d59c`, `0x8002d778`, `0x8002ca74`, and `0x8002e064`. The live PSX database now has the missing parent function object covering the `0x80012b44/60/c0/f0` lane (`psx_object_integrate_motion_and_route_visible`) plus explicit helper names/contracts for `psx_object_update_runtime_input_modes`, `psx_main_visible_list_swap_entries`, `psx_main_visible_order_graph_unlink_pair`, and `psx_main_visible_order_graph_detach_object`; direct technical decompiler comments were also applied to the update/rebucket/sort call sites and sort entry points. Current best read is tighter for exporter coherence: object update now cleanly resolves into state advance -> optional projection -> conditional rebucket -> optional nearby-interaction publish, while stage-1 draw submission remains refresh-gated and dependency-sorted before `psx_draw_world_visible_passes` consumes the slice.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now captures a focused live `SLUS_002.68` resource creation/submission cleanup around `0x80044434`, `0x800444e4`, `0x80044614`, `0x80044bdc`, `0x80044e9c`, and `0x80045ffc`. The live PSX database now also renames `0x800445c8` to `psx_overlay_slot_clone_bound_resource` and `0x80044fa4` to `psx_resource_release_image_vram_slot_if_needed`, with stale "verified by subagent pass" comments replaced by technical comments in the local HUD/overlay lane. Current best read is tighter for viewer art binding: kind `5` is the image-table submit path, kind `4` is the single-image bound sprite path, and the special-visible lane remains palette-override-distinct from main-visible despite sharing the same kind discriminator.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a focused live `SLUS_002.68` interaction/reselection cleanup around `psx_type4_reselect_motion_state` and `psx_object_update_nearby_interactions`. The live PSX database now names `0x80028050` as `psx_object_test_strict_nonoverlap_flag8_pair`, `0x800281d4` as `psx_object_test_strict_nonoverlap_flag8_subject`, `0x80028700` as `psx_object_adjust_param9c_by_view_side`, `0x800287bc` as `psx_object_update_param9c_from_contact_target`, `0x80028eb4` as `psx_object_apply_contact_push_bias`, and `0x8002923c` as `psx_object_spawn_type11_contact_proxy`, with concise technical decompiler comments added at each entry. Current best read is tighter for exporter work: post-spawn contact handling in this lane mutates runtime extents/width state (`+0x30..+0x38`, `+0x9c`) and can spawn type-`0x11` contact proxies in-flow, so unresolved-family art fallback logic should continue to model these as runtime interaction effects rather than as static constructor-placement fields.
Latest verified batch: [docs/psx/map-rendering.md](docs/psx/map-rendering.md) now records a focused live `SLUS_002.68` presentation-lane cleanup around the HUD/overlay pass and adjacent helpers. The live PSX database now names `0x80035cc0` as `psx_overlay_slot_create`, `0x80036000` as `psx_overlay_slot_release`, `0x80038114` as `psx_overlay_slot_step_color_fade`, and `0x800388a8` as `psx_hud_overlay_init_resources`, with direct technical decompiler comments replacing one earlier generic "verified by subagent pass" note at `psx_draw_hud_overlay_pass`. Current best read is tighter for viewer work: `psx_level_session_loop -> psx_hud_overlay_init_resources` seeds HUD resources during level-session startup, `psx_draw_world_visible_passes` still executes world stage-1 then stage-2 lanes before calling `psx_draw_hud_overlay_pass`, and the `psx_overlay_slot_*` table is a non-map-facing presentation queue layered after world routing rather than a source of authored map geometry. The practical implication is narrower false-match risk: unresolved map-family art fallback should not reuse evidence from this overlay slot lane, because these slots are UI/presentation primitives independent of section-0 map record dispatch and world-object projection.
Latest verified batch: [docs/regret-hidden-debugger-investigation.md](docs/regret-hidden-debugger-investigation.md) now also records the debugger-side cleanup pass after the first `.unk` loader/runtime split, the final exhaustive Regret-side caller sweep, and the first practical seeding/simulation model. The live `REGRET.EXE` database now has the source-pane constructor/pointer/draw/viewport methods, the source-buffer create/load/split/destroy chain, the breakpoint-table helpers, the current-entry push/pop helpers, the interpreter saved-farptr helpers (`13f0:0000/003c`), the interpreter-context create/init pair (`13f0:00e8/0244`), the shared slot-chunk accessor at `13f8:1d72`, and the named interpreter wrapper at `13f8:10da` that feeds `usecode_debugger_interpreter_hook`. The practical consequence is sharper than before: the remaining blocker is not `can we export readable source` or even `can compiled usecode carry line numbers`, because retail Remorse already can, and it is no longer `what else in Regret might still be debugger-related`. The remaining blocker is now narrowly `where inside the already-identified interpreter dispatcher/runtime path does Regret seed the current-entry stack, and how can that same engine-side path be reused or reproduced for stable RUN/step behavior`, with the strongest current simulation route now being a small in-process reuse of existing VM context data rather than blind external memory poking or offline-only source-file tricks.
Latest verified batch: [docs/jp-remorse-hidden-debugger-investigation.md](docs/jp-remorse-hidden-debugger-investigation.md) now records the first debugger-focused comparison pass on `/ja/CRUSADER.EXE`. Current best read is narrower than the No Regret result but still decision-relevant: the JP Win32 build clearly retains broad executable cheat/debug machinery, yet live byte searches on the active image found no hits for the classic hidden usecode-debugger UI bundle (`Goto Line`, `Watch what?`, `Inspect what?`, `Global name`, `Search for`, `FILE NOT FOUND`, `Unable to open this file`, `Nothing to find`, `Not found`, `Done`) even though the same method still recovers positive-control strings like `JASSICA16`, `Immortality enabled.`, and `Cheats are now active.`. The practical consequence is that JP currently strengthens the `broad Win32 cheat/debug preservation` story, but not the `JP preserved the missing retail debugger bootstrap` theory; No Regret remains the stronger sibling-build anchor for the hidden-debugger unlock problem.
Latest verified batch: [docs/regret-hidden-debugger-investigation.md](docs/regret-hidden-debugger-investigation.md) now records the forcing-options pass as well as the structural recovery. Current best read is now split cleanly between the analytical and practical sides: analytically, Regret still matters because it recovers the missing writer/bootstrap and the live `1480:6972` vtable override; practically, it also matters because it is now the first build where a debugger bring-up looks realistically forceable without rebuilding the subsystem. The current ranked Regret-side forcing order is: small executable patch into `usecode_debugger_open_modal` or the break/step auto-open path first, live memory forcing only if the debugger object already exists second, and `-u` / custom `EUSECODE.FLX` only as a hybrid context-generator after code or memory has already armed the debugger. That means Regret is now both the best comparison anchor for retail and the best live hack target if the immediate goal is simply to make the menu appear.
Latest verified batch: [docs/retail-debugger-entry-options.md](docs/retail-debugger-entry-options.md) now reopens the hidden-debugger entry question with the stronger current live database instead of treating it as only a patch-history problem. Current best read is now sharper in a way that affects next-step choice: fresh live data-use recovery still shows no recovered writer for `1478:659c/659e`, fresh decompiles of `usecode_debugger_open_for_current_unit`, `usecode_debugger_open_modal`, `usecode_debugger_gump_create`, and `usecode_debugger_handle_event` confirm that the debugger UI/event bundle is real but only useful after a valid break-state object and gump already exist, and the retail `-u` override remains the lowest-risk non-EXE experiment surface without yet proving a script-visible bootstrap for that object. The practical consequence is that the preferred next move is no longer more speculative retail patching first; it is a focused No Regret / JP comparison for a surviving debugger bootstrap/writer, with `-u`-backed EUSECODE experiments held as the least invasive indirect test surface.
Latest verified batch: [docs/startup-map-patch-file.md](docs/startup-map-patch-file.md) now closes the long-standing startup string `Using map patch file.` tightly enough to stop treating it as a vague debug/status artifact. Current best read is that `Init_Everything` prints that line only when `static\fixed.dat` exists, and the later fixed-map cache path then prefers the loaded `static\fixed.dat` archive handle over the base `fixed.dat` handle for map/fixed-object reads. The remaining uncertainty in this lane is now narrow: whether any later consumer does a finer-grained fallback/merge than the first recovered chooser, not what the startup line is referring to in the first place.
Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md), [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now tighten the PSX render-side model another step in both Ghidra and the viewer exporter. The earlier `DAT_800758d4` consumer finding remains intact and is still wired into the viewer-side cache path as explicit `companionExtents` metadata, but the bigger practical change in this batch is the first measured art-binding recovery pass for the viewer exporter: the PSX cache builder now treats large zero-block `DAT_800758d8` constructor-placement bands as inherited-art candidates, first via same-map `DAT_800758cc` script-signature donors and then via a constrained nearest-donor fallback inside the current `0x003e..0x0064` family. That rebuild moved the scene set from `58,262` fallback items / `1,714` bundle-mapped items to `25,038` fallback items / `34,938` bundle-mapped items, making early representative maps such as `0`, `9`, and `43` mostly real-art while leaving `map 104` and the remaining `0x0042` / `0x0055..0x0063` constructor-placement band as the clearest unresolved outliers. The newest live follow-up now sharpens the next branch enough to change execution order: `psx_draw_main_visible_object` applies authored palette overrides only for type bands `0x003e..0x00ab` (`source+0x06` high byte) and `>=0x00ac` (`source+0x0c` high byte), `psx_draw_special_visible_queue` skips those overrides entirely, and `psx_sprite_resource_submit_frame` versus `psx_image_table_submit_frame` resolve the same high-byte token through different CLUT tables. The latest coherence pass narrows that again in a way the exporter has to respect: `psx_level_session_loop` installs CLUT tables during level load via `wdl_resource_bundle_load_by_index -> level_palette_header_apply -> level_palette_upload_cluts`, both constructors bind the drawable resource at spawn from `DAT_800758d8`, `psx_object_integrate_motion_and_route_visible` advances state and routes objects into stage-1 versus stage-2 visibility, and `psx_main_visible_list_sort_range` dependency-sorts the stage-1 slice before draw submission. The newest six-track subagent sweep closes two more structural gaps: `0x0042` still shares the generic descriptor cluster used across `0x003e..0x0050` instead of owning a unique descriptor fork, and the root-dispatch `u5=0x0022` versus `u5=0x0030` split currently looks like a main-visible presentation/orientation variant rather than a separate draw lane. The viewer-side donor fence now reflects that narrower read: mixed-family and mixed-`u5` provisional donor merges for unresolved generic-family types are blocked, so `map 104` `0x0042` is back on explicit placeholders instead of one shared false wall. The practical remaining gap is therefore narrower now: not "why are most PSX scenes placeholders" and not even just "which palette index is right", but "which runtime-bank/state/resource-kind combination explains the remaining `0x0042` presentation families without reopening broad donor heuristics."
- Overall useful decompilation progress: about 59%
- Reasonable uncertainty band: about 56% to 64%
- Top 100 far-call target coverage: about 86%
- Segment spread with meaningful analysis: about 34% to 40%
- Tooling maturity for continued work: about 83%
Measured live naming floor for `CRUSADER.EXE` right now:
- total functions: `3032`
- non-anonymous functions: `1795`
- remaining `FUN_/nullfn_` placeholders: `1237`
- raw named-function coverage: `59.2%`
- largest current anonymous segment clusters: `1000` (`166`), `10e8` (`62`), `1190` (`35`), `13e8` (`23`), `13c8` (`22`)
### Why The Estimate Moved
- The NE `CRUSADER.EXE` database now has materially more named functions, better caller-role coverage, and broader comment-backed provenance than when this tracker was first drafted.
- The startup/display lane is no longer a top active section. Its outer ownership and control flow are stable enough that it should stay closed unless new caller evidence changes the model.
- The cheat/debug lane is also much tighter: the `jassica16` latch, the broader `-laurie` gate, the `~` runtime toggle, the F7-family overlays, the F10/Ctrl behavior, and the `0x410` CD-transfer-display branch are now separated well enough that this lane is mostly documentation and cleanup, not architecture recovery.
- The USECODE/VM lane has moved from broad structure guesses to a partial runtime model: core loader/runtime helpers are named, owner-loaded slot arithmetic is verified against extracted corpora, several masked-create helpers have real contracts, and the major remaining uncertainty is now the upstream selector/caller path rather than the storage format itself.
- The map-renderer crosswalk lane also removed a lot of lingering shape ambiguity by closing more controller/helper families directly from extracted corpora plus scene evidence.
- The combat-tactic data lane is also now materially tighter: `COMBAT.DAT` is no longer just a named-tactic hint source, but a documented bytecode archive with stable per-record names, verified block structure, a decoded shipped opcode subset, and a practical family-level behavior map for the `Dumb`, `Pivot`, `Advance`, `Careful`, marker-shuttle, and step-out-shoot tactics.
## Current Verified State
### Primary Tracking Assets
- `crusader_segment_coverage_ledger.csv` remains the main executable-wide coverage tracker and should be updated after each verified batch.
- `crusader_decompilation_notes.md` is an index, not the place for long-form analysis.
- `CRUSADER.EXE` remains the default live Ghidra target.
- Verified `CRUSADER-RAW.EXE` work remains a supporting evidence base for ports, naming provenance, and caller/context cross-checks.
### Strong Or Stable Areas
- seg001 gameplay/input/projectile work is stable enough to support verified raw-name ports into live NE work.
- The raw `0007` rendering/camera/tile-visibility lane has a strong structural map and now acts more as supporting evidence than as a primary unknown.
- The `0008` dispatch-helper and `000c` state/transition lanes have broad partial coverage, including enough caller-side structure to support practical NE naming work.
- The VM/USECODE lane now also has one earlier compiled-side producer anchored beyond the old direct `Item_GetDamaged` / `StorageDataProcess_Run` callers: `AreaSearch_CollideMove` is now verified as a paired `0x20b` / `0x20c` collision-process producer, and the local seg031 queue helpers are named structurally in the live database.
- That same collision-storage producer surface is now wider too: current direct callers are all movement/physics/animation-side (`Item_LegalMoveToPoint`, `Item_LegalMoveToPointWithCollisionInfo`, gravity, animation, supersprite, and fast-area gravity cleanup), and no verified non-collision producer reaches the `0x236` queue yet.
- The movement/collision lane is tighter at the helper level too: the step-aware seg029 sweep wrappers, the seg031 release-side queue cleanup pair, and the adjacent seg090 directional cache-offset helper are now named in the live database, so the remaining uncertainty in this lane sits earlier in caller policy rather than in the local helper layer.
- The startup/display lane is materially closed. Shared dispatch-entry ownership, seg126 file-backed control flow, seg127 fade control, and the surrounding palette/presentation helpers are now understood well enough that they should not stay in the live critical path.
- The cheat/debug lane is mostly closed at the behavior level. The secret-sequence matcher, broader cheat gates, F7 overlays, F10 modifier path, `Ctrl+L` location popup, `Ctrl+Q = 0x410` CD-transfer-display toggle, `-debug`, and `-laurie` are all separated far more cleanly than before.
- The hidden usecode-debugger lane is now structurally understood as a layered orphaned subsystem: seg109 UI pieces, seg1408 break-state helpers, and the seg1418 interpreter handoff are no longer conflated.
- The USECODE/VM lane now has a workable compiled-side model around `entity_vm_runtime_create`, `entity_vm_runtime_owner_resource_create`, `entity_vm_context_create_from_slot_index`, the masked-create hub at `000d:463a`, the persistence/load helpers, and the owner-loaded slot/value arithmetic.
- The owner-loaded body/range model is no longer speculative. Class-selection uses `class_id + 2`, header/subentry math matches extracted corpus output, and concrete body windows for `NPCTRIG`, `EVENT`, and related families are now verified.
- The map-renderer/documentation lane now has a stronger shape/controller crosswalk. Recent closures include `CRUMORPH`, `NPC_ONLY`, `WATCHNS`, `WATCHEW`, `CRYOBOX`, `CRAZYEW`, `CRAZYNS`, `VIDEOBOX`, `PANELEW`, `GENERATR`, and cross-game `DEATHBOX`, with viewer-side links kept conservative where actor-side state is still runtime-only.
- The command-line/startup lane is much tighter across both games: `-warp <mission> [x y z]`, `-mapoff`, `-egg`, startup teleporter selection, and the `-u` EUSECODE root override all now have practical behavior models instead of folklore-level descriptions.
- The PSX lane is no longer just side inventory. Retail/pre-alpha bundle loading, mission-briefing/passcode structure, the reduced-content pre-alpha disc, and now the retail map object's last projection stage all have dedicated notes and enough stable naming to support future targeted passes.
- The Remorse class-lift preparation lane now has a usable document cluster: overall plan, candidate inventory, endpoint spec, ABI constraints, family notes for `EntityDispatchEntry` and `SpriteNode`, a conservative `Entity` family split, a VM runtime/owner-resource layout note, a compatibility-header draft, and one grouped resume index.
- The same class-lift prep lane is now more execution-ready: the `0x4588` broker family has its own focused object note, the toolchain story has a dedicated fingerprint-evidence note, and there is now a concrete first-batch class-authoring checklist ready for the first MCP-backed namespace/struct/vtable pass.
- The live Remorse class-authoring lane now also has its first bounded NPC AI family lifted as class owners instead of flat process names: `Remorse::NPCActionProcess` plus `StandProcess`, `PaceProcess`, `SurrenderProcess`, `GuardProcess`, and `LoiterProcess` now exist in-session with their direct create/run/destroy surfaces moved under class ownership, while the shared guard/loiter idle helper remains a free function on purpose until a stronger single-class owner emerges.
- The live Remorse VM class-lift lane also recovered from a decompiler breakage in `Remorse::EntityVmRuntime::Create`: the root cause was a hidden-return-storage allocator helper signature at `1000:42e2`, `Create` now decompiles again, and the provisional `/Remorse/EntityVmSlotEntry` datatype now exists with the stable `+0x1e..+0x24` buffer-pair fields named.
- The live Remorse VM class-lift lane is tighter again: the old `UsecodeProcess_*` context lifecycle bodies at `1420:0eec`, `1420:10b6`, `1420:10da`, `1420:1162`, `1420:118f`, and `1420:1278` now live under `Remorse::EntityVmContext::{CreateFromSlotIndex, FreeBuffer, SyncGlobalValueAndDispatch, Destroy, Save, Load}`, with short raw `000d:` provenance comments preserved on each entry.
- The same VM class-lift lane tightened one step further through local PyGhidra fallback once the live `run_write_script(...)` route still returned `404 No context found for request`: `/Remorse/EntityVmContext` is now a real datatype, `entity_vm_slot_entry_create_or_clear` and `InitSlotOwnerBuffers` now carry `EntityVmSlotEntry *`, `AcquireSlotForEntity` now returns `EntityVmSlotEntry *`, and `InitSlots` / `ReleaseSlots` now take direct `EntityVmRuntime * this`.
- That pass also made the remaining blocker more precise: `Create` still cannot hold a fully typed far `this` without reintroducing hidden `__return_storage_ptr__` corruption, so it was restored to the verified split-word custom-storage signature instead of forcing a broken prettier form.
- Tooling follow-up from that same batch is now clearer too: live MCP read-only Python is usable when Ghidra starts with PyGhidra enabled, but write-side repairs still had to fall back to closed-project local PyGhidra because MCP does not yet expose a constrained live write-script or equivalent custom-storage edit path.
- The live VM class-lift lane tightened slightly again in-session: `1420:19fd Remorse::EntityVmRuntime::EnsureSlotChunkLoaded` now carries a real `EntityVmSlotEntry *` local for the acquired slot path, so the slot-entry cache tail fields decompile directly instead of through anonymous `undefined4` pairs.
- The matching MCP gap is also clearer now: the old `apply_class_layout` dry-run null failure no longer reproduces for `/Remorse/EntityVmContext`, but the real write path still behaves like the older storage-preserving build. Actual `apply_class_layout` and direct `set_function_this_type` calls on the context lifecycle methods still fail with `Storage size does not match data type size: 2`, and live `run_write_script(...)` still returns `404 No context found for request` even with explicit target selectors.
- Closing the GUI and dropping to the local PyGhidra fallback then landed the blocked context typing work cleanly: `CreateFromSlotIndex`, `FreeBuffer`, `SyncGlobalValueAndDispatch`, `Destroy`, `Save`, and `Load` now all carry `EntityVmContext * this` as their first parameter in `CRUSADER.EXE`, which confirms the newer dynamic-storage rewrite is sound even though the live MCP session still is not taking it.
- The next live verification pass tightened two details. First, the new checked-in storage-aware prototype endpoint still is not the build currently serving the active GUI session: direct live POSTs to `/set_function_prototype_storage` still answered with the legacy `set_function_prototype` failure body, and the alias route still returned `404 No context found for request`. Second, the direct callers of `CreateFromSlotIndex` still mostly consume the result as a base process object, so the current conservative `UsecodeProcess *` return should stay in place until the inheritance-aware datatype story is explicit.
- The refreshed live MCP build moved that forward materially: `set_function_prototype_storage(...)` now reaches the real storage-aware implementation in-session and the active-program `run_write_script(...)` path now executes instead of failing with `404`. The new blocker is narrower and more concrete: bare `stack:` offsets at `10` and above currently need `0x` prefixes to preserve the intended stack slots, `__cdecl16far` still normalizes to plain `__cdecl`, and `Create` still cannot collapse to a single `EntityVmRuntime * this` because the datatype itself still resolves to a 2-byte pointer size.
- The same live batch also tightened the slot-entry class model: `/Remorse/EntityVmSlotEntry` now carries `match_key_farptr`, `owner_chunk_count`, and `owner_data_base` in addition to the earlier owner-buffer and chunk-state tails, which makes `InitSlotOwnerBuffers`, `AcquireSlotForEntity`, and `EnsureSlotChunkLoaded` read more like object code and less like anonymous offset arithmetic.
- The next live batch tightened the adjacent helper map too: the old unnamed `1420:1d72`, `1420:1d8d`, and `1420:1e17` helpers are now `entity_vm_runtime_get_slot_chunk_ptr_at_offset`, `entity_vm_runtime_release_slot_chunk_ref`, and `entity_vm_runtime_try_unload_slot_chunk`, which makes the slot-entry lifecycle around load, refcount release, and conditional unload materially easier to navigate.
- The latest live batch turned that helper lane into a small shared record model: `/Remorse/EntityVmLoadedChunkRecord` now carries the stable `next_*`, `saved_chunk_*`, `slot_index`, and `chunk_index` anchors, `entity_vm_runtime_try_unload_slot_chunk` now takes `EntityVmLoadedChunkRecord *` and returns `byte` in `AL`, and `entity_vm_runtime_apply_to_matching_owner_rows` now iterates over a typed loaded-chunk record instead of anonymous stack-pair scratch state.
- The adjacent interpreter-side lane is slightly tighter too: local helper `1418:003c` is now `interpreter_pop_saved_farptr`, and the only verified `Interpreter_NextUsecodeOp` release path at `1418:3330` is commented as a save/restore boundary around `entity_vm_runtime_release_slot_chunk_ref` instead of being left as anonymous stack traffic.
- The live class-authoring state moved forward too: `Remorse::EntityVmSlotEntry` now exists as a real class owner in `CRUSADER.EXE`, `CreateOrClear` moved under it with an explicit `this` parameter and `AX` pointer return, and the runtime-local chunk helpers plus owner-row iterator/debug path now sit under `Remorse::EntityVmRuntime` instead of Global.
- The next live pass improved the runtime class surface further: `GetSlotChunkPtrAtOffset` now carries the recovered `runtime_farptr/slot_index/chunk_index/intra_chunk_offset` signature and still returns its far pointer in `DX:AX`, while `ApplyToMatchingOwnerRows` now carries the recovered `runtime_farptr/slot_index_filter/chunk_index_filter` signature and still returns its boolean in `AL`.
- The latest live pass removed the old runtime-wide 2-byte-`this` bottleneck for this cluster: `Create`, `InitSlots`, `ReleaseSlots`, `DebugDumpSlotMemory`, `ReleaseSlotChunkRef`, `GetSlotChunkPtrAtOffset`, `TryUnloadSlotChunk`, `ApplyToMatchingOwnerRows`, and `EnsureSlotChunkLoaded` now all accept an explicit 4-byte `EntityVmRuntime * this` through `/Remorse/EntityVmRuntime *32` custom storage in-session. The remaining live type gap is narrower again: exact `/Remorse/EntityVmSlotEntry *32` return/parameter typing still fails on `AcquireSlotForEntity` and `InitSlotOwnerBuffers`, so those positions are currently held as neutral `dword` placeholders instead of prettier but broken slot-entry pointer types.
- That slot-entry gap is now closed too, and the pointer cleanup widened beyond the runtime core: `AcquireSlotForEntity` now returns `EntityVmSlotEntry *32`, `InitSlotOwnerBuffers` now accepts `EntityVmSlotEntry *32`, `EntityVmOwnerResource::{Create,Destroy}` now carry explicit 4-byte `this`, and the simple `EntityVmContext` lifecycle methods now do the same. The main remaining VM signature outlier is `CreateFromSlotIndex`, whose argument pack still needs caller-side recovery rather than just pointer-width cleanup.
- The next family switch also landed: `Remorse::UsecodeDebuggerBreakState` now exists as a real class owner with a `0x2f2` provisional datatype plus a first method batch for construction, breakpoint gating, breakpoint table helpers, callstack helpers, and step-state helpers.
- That debugger batch is already tighter than the initial shell: `1408:01a5` is now verified as `BreakpointRemove`, `1408:02f5` is now verified as `CallstackPushFrame`, breakpoint entries are recovered as `0x0b` inline-name-plus-line records, and callstack entries are recovered as `0x15` inline-name-plus-three-dword records even though the trailing dword semantics remain open.
- The next pass landed the debugger struct rewrite in-session too: `/Remorse/UsecodeDebuggerBreakpointEntry`, `/Remorse/UsecodeDebuggerCallstackEntry`, and the updated `/Remorse/UsecodeDebuggerBreakState` array layout now exist live instead of only in notes, and the only verified `CallstackPushFrame` caller now narrows those three trailing dwords to `source_stream_target_farptr`, `current_frame_payload_farptr`, and still-neutral `aux_farptr`.
- The latest debugger class-lift pass closed two more bounded gaps without overpromoting semantics: `1408:0230` now lives under `Remorse::UsecodeDebuggerBreakState::BreakpointFindFirstForUnitAtOrAfterLine` as the breakpoint-table lower-bound helper for `(unit_name, line_number)` queries, and the retail vtable root at `1478:65ab` is now resolved enough to show that `MaybeBreakOnCurrentLine` dispatches slot 0 into a shipped no-op stub while slot 1 currently returns zero through a second inert method.
- The next debugger follow-up also closed the planned seg109 consumer pass: `13a0:0291` plus its helper `13a0:045c` now show that the current callstack entry's `+0x09` lane is a real source-stream cursor consumed byte-by-byte by the debugger formatter and that `+0x0d` is the paired current-frame payload context used for expression/watch rendering. The remaining open tail-field question is now mostly `aux_farptr`, not the first two dwords.
- That naming decision is now landed live rather than only in notes: `/Remorse/UsecodeDebuggerCallstackEntry` now names offset `+0x09` as `source_stream_cursor_farptr` with an in-session field comment, and `CallstackPushFrame` now carries the same parameter name in its signature. The debugger-family residue is therefore narrower again: mainly `aux_farptr`, plus whether the seg109 formatter helpers deserve stable names.
- That last formatter-helper hesitation is now closed too. The seg109 consumer pair is no longer anonymous in-session: `13a0:0291` now lives as `usecode_debugger_format_expression_to_shared_buffer`, and `13a0:045c` now lives as `usecode_debugger_format_descriptor_expression`. The debugger-family residue is therefore narrower again: mainly `aux_farptr`, plus any future evidence that the retail-stub callback slots ever had non-retail behavior.
- The follow-up retail caller pass did not widen `aux_farptr` either. `get_callers(1408:02f5)` still reports only `1418:051d Interpreter_NextUsecodeOp`, that caller still pushes literal zero for the trailing field, and the current seg109 formatter consumers still read only `+0x09` and `+0x0d`. For now the right live result is to keep `aux_farptr` intentionally neutral rather than invent a prettier but weak name.
- The next bounded class-family step landed too. `Remorse::SpriteNode` now exists live in `CRUSADER.EXE`, and the first strong `000b:` batch is re-anchored into live `1360:` by preserved offset delta from `000b:326e -> 1360:046e`: `Destroy` (`1360:046e`), `IsDirty` (`1360:0580`), `MarkDirty` (`1360:05a6`), `DispatchEvent` (`1360:0cb2`), and `UpdateAndDispatch` (`1360:12ee`) are now class-owned with in-session provenance comments. The remaining `SpriteNode` work is narrower and safer than before: mainly the constructor side, the exact live anchor for `GetOrTraverse`, and later vtable/datatype authoring rather than basic family existence.
- That same `SpriteNode` pass also moved beyond method ownership into datatype work: `/Remorse/SpriteNodeBase` now names `child_or_next_farptr`, `local_x_offset`, `local_y_offset`, and `dirty_flags`, and `/Remorse/SpriteNodeVtable` now exists as a provisional slot shell exposing `+0x14`, `+0x18`, `+0x20`, and `+0x24`.
- The constructor side is now started too: `1360:036a` lives as `Remorse::SpriteNode::Create` with an in-session caveat comment that preserves the remaining wrapper uncertainty. The live search for the old `000a:b988 GetOrTraverse` anchor is still open, but the family no longer lacks a constructor-style entry outright.
- That remaining traversal gap is now closed too. `1360:0955` now lives as `Remorse::SpriteNode::GetOrTraverse`, and the decompiler comment records the currently safest read of the helper: recurse over child-linked nodes, adjust the incoming query coordinates by the local offsets, and return either the matched node or the default sentinel through the out pointer. The main `SpriteNode` residue is therefore structural again: constructor-wrapper split, deeper slot naming, and subtype layout boundaries.
- The next bounded-family start is now landed too. `Remorse::CacheBackendObject` exists live with `1250:0000` promoted as `Create`; the decompiler itself carries explicit old `0009:5600` segment metadata on that body, and the current comment records the `0x20`-byte allocation plus file-handle/method-table initialization path. That family is still only at its constructor shell, but it is now a live class-lift lane instead of a pure inventory entry.
- The broader Tier 1 Remorse class sweep is now closed too. `EntityVmOwnerResource` gained two real accessor wrappers in-session (`QueryMaterializationSize` and `MaterializeChecked`) plus a corrected outer-wrapper layout (`0x14` bytes total, embedded file base at `+0x00..+0x07`, helper vtable at `+0x08`, owner-row table at `+0x0c`); `CacheBackendObject` gained the first two non-constructor class methods (`LoadEntryTableFromManifest` and `InitFixedEntryTable`) plus a tighter live layout read around `+0x10/+0x14/+0x16/+0x18/+0x1c`; and `SpriteNode::DispatchEvent` now ties concrete event codes to concrete vtable slots instead of generic placeholder slot names.
- The next broader Remorse batch also has its first post-Tier-1 live foothold now. `PresentationCallbackBroker` is no longer note-only: `12d0:0513` and `12d0:0656` are now live as `Remorse::PresentationCallbackBroker::{InitOnce, TeardownOnce}` with comments tied directly to the `0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6` lifecycle cluster. The same pass also clarified that `WatchEntityController` and `DialogMenuObject` still need a second re-anchor pass before any live authoring: first-pass searches on the obvious type/vtable/callback constants hit unrelated camera/process and controller-save functions rather than safe class-family matches.
- That second pass is now partly closed. The old `WatchEntityController` create lane maps onto the live `Camera_Init` / `Camera_CreateProcess` cluster at `1180:0000/0045`, so those functions now carry provenance comments instead of a weaker forced rename; `DialogMenuObject` still lacks a safe live re-anchor after a second search on the obvious `0x28b5/0x27ca/0x2843` leads; `PresentationCallbackBroker` now has its raw `0009:b1c3` finalize-phase caller re-anchored live as `allocator_phase_finalize_pass` plus two preserved live slot `+0x0c` callers at `1278:0616` and `1320:1588`; `CacheBackendObject` gained `SetEntryNameAndTag` at `1250:0910`; and the widened `SpriteNode::Create` caller map now shows that the `0x34` allocation path is the compact shared node constructor used by many `GumpCreate_*` wrappers.
- The next planned pilot family also started for real: `Remorse::EntityDispatchEntry` now exists in-session with provisional `/Remorse/EntityDispatchEntryBase` and `/Remorse/EntityDispatchEntryVtable` datatypes, so this family is no longer just a note cluster. The remaining blocker is now concrete rather than vague: the current source note still points at older `0008:` / `000d:` anchors that are not yet ported back onto the live `CRUSADER.EXE` method objects, so the first base-method ownership move has to wait on that mapping step instead of being guessed.
- That mapping step is now partially closed too. The older `0008:ba00` base cluster ports into live `11e0:` by offset, and the first base-method batch now lives under `Remorse::EntityDispatchEntry`: `InitBase`, `SetSourceType`, `SetEventTypeChecked`, `SetGroupId`, `Unlink`, and `IncrementGroupId`. The next blocker on this family is therefore narrower again: not whether the pilot can move methods at all, but which live segments carry the remaining word-list, timed/periodic, and runtime-state methods from the older `0008:` / `000d:` notes.
- The runtime-state follow-up is now partially closed too. `FadeProcess_Create` is explicitly tagged by the decompiler as old `000d:7e00`, `FUN_1440_0278` matches the old `000d:8078` release path by both offset delta and behavior, and both now live under `Remorse::EntityDispatchEntry` as `InitRuntimeState` and `ReleaseRuntimeState` with a new `/Remorse/EntityDispatchEntryRuntimeState` overlay datatype. That leaves the remaining `EntityDispatchEntry` pilot work in a narrower end-of-day state: mainly the word-list destroy lane and the timed/periodic constructor cluster, not the core base or runtime-state surfaces.
- That pilot moved one more bounded step in-session too. The periodic/timed branch from the old `0008:` note cluster is now re-anchored live onto `11e0:` well enough to move six more methods under `Remorse::EntityDispatchEntry`: `ConstructVtable3AD2` (`11e0:14fb`), `ConstructVtable3AA6` (`11e0:1814`), `SetUpdatePeriodAndReschedule` (`11e0:187e`), `TickPeriodic` (`11e0:1913`), `EnableActiveCounters` (`11e0:19e6`), and `DisableActiveCounters` (`11e0:1a33`). Each now has an in-session provenance comment tying it back to the old `0008:` anchor, so the remaining `EntityDispatchEntry` blocker is narrower again: the word-list-owned subtype still has no live function objects in the expected `11e0:2000..25a1` window, and a bounded boundary scan did not yet yield safe entries to promote.
- That remaining `EntityDispatchEntry` blocker is now closed by a re-anchor correction. The expected `11e0:2000..25a1` window is not code in the current live database; the old `0008:da00..dfa1` word-list-owned subtype actually lives in the `11e8:` `MList_*` cluster, with `11e8:0000` carrying explicit old `0008:da00` segment metadata in the decompiler. That full batch now also lives under `Remorse::EntityDispatchEntry`: `SetWordList0408Terminated`, `FreeWordList`, `Destroy`, `EnsureWordListContains`, `AppendUniqueWord`, `RemoveWordValue`, `GetWordAt`, `SetWordAt`, and `FindUnflaggedWordById10`, each with an in-session provenance comment. The remaining question on this pilot family is therefore modeling depth rather than location: whether the `11e8:` word-list branch deserves its own explicit derived/overlay datatype instead of remaining a method cluster under the shared class owner.
- `CreateFromSlotIndex` is no longer a raw anonymous pack either: the live signature now separates `owner_source_farptr`, `pitemno_farptr`, `mode_flags`, `slot_index`, `value_add_offset`, `intra_chunk_offset`, `ucparam_farptr`, and `ucparamsize`, with explicit `AX:DX` return storage restored even though the endpoint still textualizes the function conservatively as plain `dword __cdecl`.
### Areas That Are No Longer Live Priorities
- Startup/display transition recovery is no longer a front-line blocker unless overlap repair becomes necessary for adjacent work.
- The general cheat/debug key matrix no longer needs broad exploratory work.
- The `-debug` switch is no longer an open mystery; remaining work there is mostly sink-side cleanup and documentation.
- The earlier executable-patch experiments around the hidden debugger are documented history, not a current decompilation priority unless new evidence changes the entry model.
## Live Blockers
1. The main remaining VM uncertainty is the real upstream selector/caller path into `entity_vm_opcode_sequence_run` and adjacent masked-create helpers. One earlier producer is now closed at `AreaSearch_CollideMove` for the `0x236` collision-storage family, but the owner-loaded class-family chooser and any broader non-collision producers are still upstream-dark.
2. The dark masked-materializer wrappers still need caller-role recovery, especially the signed-additive slot-`0x0a` / slot-`0x0b` pair and the surrounding higher-slot wrapper ladder.
3. The callback object rooted at `0x4588` still lacks a behaviorally safe subsystem name even though its allocation/finalize neighborhood is better constrained.
4. A few hot or awkward function ranges still lack clean function objects or good boundaries, especially around `000c:db68`, `000e:ffb0`, and several caller-dense gaps in `0007`, `000b`, and `000e`.
5. Weakly covered resource/data-loader families and non-`CALLF` far-pointer relocations are still a second-pass blocker for some object/table recovery work.
6. The segment ledger has improved, but it still trails the actual verified state in the notes and Ghidra database. Promoting known segments from documented evidence remains real work, not bookkeeping trivia.
## Current Focus
1. Keep the live NE `CRUSADER.EXE` lane as the default working surface, using raw/full-EXE and standalone-segment work only as supporting evidence.
2. Keep the VM/USECODE lane focused on selector recovery, caller-role recovery, and record-shape confirmation rather than repeating storage-format validation that is already closed.
3. Promote ledger coverage from existing verified notes before broadening into fresh executable-wide sweeps.
4. Use overlap repair only where it unlocks an active high-payoff lane.
5. Use the map-renderer/tooling lane to validate shape ids, map placements, and viewer semantics before promoting additional static-object names in Ghidra.
6. Keep the PSX lane focused on the final lane-aware state/resource/frame/palette bridge now that the first post-spawn interaction/reselection cluster is named; avoid broad renderer-side heuristics that bypass those runtime paths or flatten main-visible and special-visible palette behavior together.
## Next Resume Point
1. Resume the hidden-debugger lane from [docs/regret-hidden-debugger-investigation.md](docs/regret-hidden-debugger-investigation.md), [docs/jp-remorse-hidden-debugger-investigation.md](docs/jp-remorse-hidden-debugger-investigation.md), and [docs/retail-debugger-entry-options.md](docs/retail-debugger-entry-options.md): use No Regret, not JP, as the primary sibling-build anchor and compare retail `CRUSADER.EXE` directly against the now-closed Regret bootstrap/state-hook/runtime map. The concrete next target is no longer another broad Regret sweep; it is the smallest retail analogue of Regret `1398:0000`, the missing writer for retail `1478:659c/659e`, and any retail interpreter-side handoff that could be reattached without the wider old patch chains.
2. In parallel with that comparison, keep the `-u` / replacement-`EUSECODE.FLX` lane alive as the least invasive practical experiment surface: prefer monitor/computer, `SURCAM*`, and `NPCTRIG` families over generic container scripts, and look only for indirect compiled control bridges rather than assuming usecode can already construct the debugger state directly.
3. Continue broad sweeps in the same `12f8` / `13c8` / `13f8` UI-gump neighborhood so the next write window can harvest more obvious virtual-slot and wrapper names before switching back to deeper caller-policy work.
4. Resume from `docs/ne-hole-filling-priorities.md` only after the current UI edge stops yielding cheap structural wins; the immediate best candidates are the remaining anonymous sibling methods in the main-menu, quick-save/load/exit, and adjacent button-gump families.
Current side-cluster progress: the live session now has named media/audio helpers (`FlicPlayProcess_Destroy`, `FlicWaitProcess_Destroy`, `MusicPlayerProcess_RunNoop`, `MusicPlayerProcess_Destroy`, `AssProcess_Destroy`, `FlicWaitProcess_VtableSlot10TickAndMaybeAdvance`, `MusicPlayerProcess_VtableSlot10Noop`, `AssProcess_VtableSlot5ClearCreatedFlag`, `AssProcess_VtableSlot6SetCreatedFlag`, `VideoPlayer_InitializePlayback`, `VideoPlayer_OpenMediaFiles`, `VideoPlayer_AllocPlaybackBuffers`, `VideoPlayer_OpenMoviListAndPrimeStreams`, `VideoPlayer_StopAndDestroyWrapper`, `VideoPlayerProcess_VtableSlot11Noop`, `File_Exists`, `VideoPlayer_FormatErrorMessage`, `VideoPlayer_AdvanceChunkCursor`, `VideoPlayer_AdvanceChunkCursorWrapper`, `VideoPlayer_LoadAudioChunk`, `VideoPlayer_LoadVideoChunk`, `VideoPlayer_BlitDecodedFrame`, `Music_RestorePreviousTrackFromStack`, `Music_LoadStateAndReplayCurrentTrack`, `Music_SaveState`, `ASS_StoreInitCallbackState`), a fully named savegame UI cluster (`SavegameNameField_MapInputChar`, `SavegameMenu_Destroy`, `SavegameMenu_HandleKey`, `SavegameMenu_HandleSlotAction`, `SavegameSlot_DrawCornerDecorations`, `SavegameSlotGump_Create`, `SavegameSlotGump_Destroy`, `SavegameNameField_HandleKey`, `SavegameSlot_HandleClick`, `SavegameSlot_BeginEditOrActivate`, `SavegameNameField_Draw`, `SavegameSlot_Select`, `SavegameSlot_GetLabelPtr`, `SavegameSlot_SetLabel`, `File_CloseAndMaybeFree`), a newly named main-menu shell (`MainMenu_Destroy`, `MainMenu_DrawCornerDecorations`, `MainMenu_HandleButtonClick`, `MainMenu_HandleKey`, `MainMenu_ActivateSelection`) plus one additional main-menu subcluster (`MainMenuOptionsPanel_Create`, `MainMenuOptionButtonGump_Create`, `MainMenuOptionButtonGump_HandlePointerEvent`, `MainMenuOptionButtonGump_SelectPeer`, `MainMenuOptionButtonGump_Draw`), a tightened help-gump subcluster (`HelpGump_RefreshPage`, `HelpGump_HandleAdvanceAction`, `HelpGump_HandleNavigationKey`, `HelpGump_RunAmbientSfxTick`), a cleaned-up `10f8:` item-type helper pair (`ItemScript_AppendBytes`, `ItemTypeflagRecord_ResetDefaults`), a large ownership-backed process cleanup batch (`MapJumpProcess_Destroy`, `FadeProcess1_Destroy`, `AnimProcess_Destroy`, `ItemProcess_Destroy`, `SuperSpriteProcess_Destroy`, `OneFrameDelayProc_Destroy`, `CameraProcess_Destroy`, `KeyDaemonProcess_Destroy`, `KeyboardProcess_Destroy`, `AccWaitProcess_Destroy`, `SystemTimerProcess_Destroy`, `BiosProcess_Destroy`, `CustomWaitProcess_Destroy`, `DumbTimerProcess_Destroy`, `CycleProcess_Destroy`, `FadeProcAlt_Destroy`, `MyTimerProcess_Destroy`), a companion broad slot-method batch (`MapJumpProcess_VtableSlot10AdvanceItemFind`, `AnimProcess_VtableSlot10DispatchByPort`, `FadeProcess2_VtableSlot10BlendTowardTargetPalette`, `AttackProcess_VtableSlot10DispatchByClip`, `WaitProcessFamily_VtableSlot10DispatchByPair`, `AccWaitProcess_VtableSlot10DispatchByAnimation`, `BiosProcess_VtableSlot10DosRealFarCall`, `CustomWaitProcess_VtableSlot11ArmAndRun`, `MyTimerProcess_VtableSlot10IncrementCounterOnTick`, `BaseCameraProcess_VtableSlot10SetViewportRect`, `BaseCameraProcess_VtableSlot11FreeBuffer`), a broad UI/gump ownership cleanup batch (`StdIntHandlerProcess_Destroy`, `GumpShared_DestroyNoop`, `KeyboardInputHandler_DestroyNoop`, `GumpShared_VtableSlot10Noop`, `KeyboardInputHandler_VtableSlot10Noop`, `KeyboardInputHandler_VtableSlot11Noop`, `ButtonGump_Destroy`, `KeypadGump_Destroy`, `KeypadButtonGump_Destroy`, `HelpGump_Destroy`, `RunCreditsProcess_Destroy`, `QuickSaveLoadExitGump_Destroy`, `Gump13f80383_Destroy`, `Gump13f80383_Draw`), another structural process-family cleanup batch (`AnimProcess_RunNoop`, `Process1048_0000_RunNoop`, `Process1048_0000_Destroy`, `AnimPrimitiveProcessSomethingElse_Destroy`, `AnimPrimitiveProcessFamily_VtableSlot11CallSlot3`, `Process1188_0000_RunOnTimerDelta`, `Process1188_0000_Destroy`), and a final tiny conservative broad-sweep batch (`SystemTimerProcess_RunNoop`, `Gump13f80383_VtableSlot10Noop`, `Gump13f80383_VtableSlot11Noop`). The next defensible step can now keep sweeping broadly for ownership-backed leftovers, push deeper into subordinate menu/dialog families, or return to unfinished media helpers.
The latest micro-batch also corrected one structural naming mistake in the shared gump lane: `GumpShared_VtableSlot3Noop`, `GumpShared_VtableSlot7Noop`, `GumpShared_VtableSlot8Noop`, `GumpShared_VtableSlot9Noop`, `GumpShared_VtableSlot16Noop`, and `GumpShared_VtableSlot17Noop` now replace the older keyboard-only labels after direct table reuse showed those no-op slots are shared by help/menu/gump families.
The newest broad-sweep UI batch tightened three more local families without needing deeper subsystem claims: `GumpShared_DestroyCommon` is now the shared gump base destroy helper at `12f8:02e4`; the quick save/load/exit modal now has `QuickSaveLoadExitGump_Create`, `QuickSaveLoadExitGump_HandleChildButtonEvent`, `QuickSaveLoadExitGump_HandleKey`, and `QuickSaveLoadExitGump_DrawLabel`; the adjacent main-menu options-panel wrapper lane now has `MainMenuOptionsPanelButtonGump_Create`, `MainMenuOptionsPanelButtonGump_DrawLabel`, `MainMenuOptionsPanelButtonGump_Select`, and `MainMenuOptionsPanelButtonGump_Deselect`; and a second `13c8:` options-menu lane now has `MainMenuOptionsMenu_{Create,Destroy,GetOptionRect,HandleChildButtonEvent,HandleKey,DrawTitle}` plus `MainMenuOptionsMenuButtonGump_DrawLabel`. The next low-risk follow-up in this same neighborhood is therefore narrower again: remaining anonymous sibling methods in `13c8:` / `13f8:` and any matching button-gump virtual slots in `1308:` that can be named structurally from local family behavior.
5. Stay on the VM lane and move one step earlier than the now-mapped movement/collision helper set around `AreaSearch_CollideMove`: the local seg029/031/090 helper layer is now named, so the next work is the policy/dispatch layer that decides when those legal-move, gravity, animation, or supersprite paths instantiate the local `0x236` collision-storage queue, plus verification of whether any non-collision producer feeds the same `StorageDataProcess_Create` / `Run` family.
6. Recover caller roles for the remaining dark signed-additive masked wrappers, especially the slot-`0x0a` / slot-`0x0b` pair, and compare them against the now-anchored slot-`0x12` caller pattern.
7. Tighten the higher-slot wrapper ladder around `0005:3115..31da` so future event-label promotion depends on compiled caller behavior instead of external tables.
8. Tighten the seg006 masked-helper caller chains so the local state-selector/value family can be tied to concrete gameplay subsystems.
9. Classify the paired seg070 loops behind `entity_vm_runtime_owner_resource_create`, especially which temporary buffers and record schemas each family populates.
10. Stay on the Remorse VM class-lift batch while the repaired runtime lane is warm: use the now-recovered `CreateFromSlotIndex` caller pack to decide whether any remaining scalar positions deserve stronger typedefs, but keep the return semantically conservative until the base-process inheritance model is explicit enough to justify a prettier live return type.
11. Stay on the new Remorse NPC action-process class-lift lane while seg033 is warm: the owner-first move is done, so the next bounded pass should recover process-function-table slot order, inspect `StandProcess::Run` and `PaceProcess::Run` for equally strong family-local helpers, and decide whether a provisional `/Remorse/NPCActionProcess` datatype is safe or whether the family should remain owner-only for now.
12. The current broader Remorse follow-up batch is now materially tighter: `WatchEntityController` is effectively re-identified as the live camera-process create lane, `DialogMenuObject` is the last compact family here without a safe live re-anchor, `PresentationCallbackBroker` now has install/teardown plus both slot `+0x08` and preserved slot `+0x0c` caller evidence, `CacheBackendObject` has its indexed entry writer, and `SpriteNode::Create` now looks like the shared compact node constructor for `GumpCreate_*` wrappers. The clearest next unresolved items are therefore: a safer live reanchor for `DialogMenuObject`, a decision on whether the camera-process lane should stay under the stronger live `Camera_*` naming or also receive a class-owner layer, deeper slot `+0x0c` payload classification in the broker lane, and higher-level subtype/layout work above the compact `SpriteNode` base.
13. In the local GhidraMCP upgrade lane, add support for dual POST body decoding (`application/json` plus form-urlencoded) and a constrained live write-side PyGhidra endpoint family so future custom-storage/type repairs can stay inside the active MCP session when Python is enabled.
14. Promote additional ledger rows directly from already-verified docs and live comments, especially where segments already deserve `Foothold`, `Partial`, or `Deep`; the new seg029 step-aware sweep batch, seg031 queue-release batch, seg090 movement-helper batch, seg033 NPC-process foothold, and seg032 item-type foothold should be the immediate template.
14. If the VM lane stalls, revisit `000e:ffb0` from the now-better-constrained video/audio caller windows and try to recover an adjacent non-overlapped helper before attempting broad boundary repair.
15. Continue the map-renderer cross-check lane by building one conservative shape-id/map-placement crosswalk from `shapedata_more_complete.txt`, extracted corpora, and authored scene evidence before promoting more trigger-heavy classes in NE.
16. Keep the PSX pre-alpha lane alive as a secondary target: classify the `LoadExec` callers, test whether the stale `TALK1.XA` path is still reachable, and compare the shipped `LSET1` bundles against the retail extractor outputs.
17. Continue the retail PSX state/art lane from the new art-binding recovery baseline: keep `DAT_800758d4` on the runtime-bounds side unless new family-specific evidence contradicts it, treat `map 104` plus the remaining `0x0042` / `0x0055..0x0063` zero-block constructor-placement band as the primary regression target, and trace the next family-specific callers around `psx_type4_reselect_motion_state`, `FUN_80028c94`, constructor-side resource creation, and the drawable-resource/frame submission lane until the remaining donor-based fallback logic can be replaced with an executable-backed lane-aware alias/resource/frame/palette rule. The first practical split to preserve is now explicit in the live database: main-visible objects can consume authored override bytes from different source offsets by type band, while the special-visible queue does not, and the next renderer-side check should keep the full coherence order intact: load-time CLUT install, spawn-time resource bind, frame-time state advance and routing, then stage-1 dependency sort before submission.
## Remaining Work To Reach A Reasonably Complete Decompilation State
### 1. Coverage And Tracker Completion
- Keep turning the seeded 145-row ledger into a trustworthy whole-program dashboard.
- Sweep remaining lightly covered segment clusters by adjacency and call relationships rather than one-off function hunting.
- Keep the plan, the docs, the ledger, and the live Ghidra comments synchronized after each verified batch.
### 2. VM / USECODE / Scripting Lane
- Close the upstream selector/caller path into the sequencer and masked-create families.
- Finish separating owner-row-backed data from runtime-decoded control streams and dispatch-entry seed records.
- Expand caller-backed event-label promotion only where binary behavior and slot reuse agree.
- Keep maturing the tooling bridge from extracted corpora into compiled-side annotation/import workflows.
### 3. Callback / Allocator / Object-Role Lane
- Classify the `0x4588` callback object strongly enough for a real subsystem name.
- Separate generic cache/allocator mechanics from game-specific client behavior where caller evidence supports it.
- Keep low-level helper names conservative until behavior, not just structure, is clear.
### 4. Rendering / Animation / UI Support Lanes
- Keep the rendering/palette/animation lanes focused on caller-side semantics and cleanup, not exploratory renaming in isolation.
- Revisit `000e:ffb0` and adjacent overlap-heavy video helpers only when the payoff is clear.
- Use map-renderer evidence and extracted corpora to validate static-object and helper/controller naming before promoting it into live NE work.
### 5. Data / Resource / Relocation Coverage
- Tackle deferred non-`CALLF` far-pointer relocations when they are needed for active table/object recovery.
- Broaden weakly covered resource/data-loader families where they block real subsystem classification.
- Keep external references like ScummVM or older disasm corpora as evidence aids, not rename authority.
## Priority Order
1. VM / USECODE selector and caller recovery
2. Coverage-ledger refinement from already-verified notes
3. Callback-object classification around `0x4588`
4. High-value boundary repair when it unlocks active work
5. Broader segment sweeps and second-pass data/relocation work
6. Secondary map-renderer and PSX follow-up lanes
## Evidence Anchors
Primary files backing this plan state:
- `crusader_segment_coverage_ledger.csv`
- `crusader_decompilation_notes.md`
- `docs/overview.md`
- `docs/ne-hole-filling-priorities.md`
- `docs/crusader-disasm-reference.md`
- `docs/raw-porting-progress.md`
- `docs/raw-0008-000c.md`
- `docs/raw-000a-000d.md`
- `docs/raw-000e.md`
- `docs/far-call-targets.md`
- `docs/usecode-roundtrip-ir.md`
## Update Rule
Update this file when one of the following happens:
- the headline estimate changes materially,
- a live blocker is resolved,
- a subsystem moves from structural to behavioral understanding,
- a segment cluster is promoted materially in the ledger,
- or the next resume point changes enough that the current handoff would mislead the next pass.
Keep this file short. Move detailed completed analysis into the appropriate file under `docs/` and leave only the current state, blockers, and forward path here.