Crusader_Decomp/docs/psx/jl-9-investigation.md
2026-04-12 14:45:08 +02:00

155 KiB

PSX JL-9 / Hidden Passcode Investigation

This note is the compact fact-first summary for the active PlayStation SLUS_002.68 investigation around JL-2, JL-9, hidden passcodes, the extra post-cheat weapon lane, the checked VRAM dump, and the checked 2 MiB main RAM dump.

Natural in-level gate-arm event work now has its own companion note: docs/psx/jl-9-in-level-event.md.

Executive summary

Authored producer reachability correction pass (2026-04-12 live MCP)

This pass pushed farther on the authored producer side across psx_load_type_state_banks -> psx_object_create_simple_record -> psx_run_object_behavior_program_tick -> psx_object_behavior_opcode_dispatch -> psx_behavior_subopcode_dispatch -> psx_level_gate_slot_dispatch_from_action_record.

Strongest producer-context update

  1. Loader/constructor provenance remains closed:
  • psx_load_type_state_banks (0x8003917c) installs psx_type_simple_component_bank[type] from level/LSET payload.
  • psx_object_create_simple_record (0x800249f4) seeds component program_base/pc from that bank.
  1. Behavior tick guard was revalidated at instruction level:
  • 0x80026710 checks (record_word0 - 1) < 0x0a, which is arg-count bound.
  • opcode is loaded separately from record_word1 (lw a0,0x4(a3)), so opcode 54 is not excluded by this guard.
  1. Subdispatch linkage is therefore viable as authored-program context (not guard-blocked):
  • opcode table entry 54 at 0x80064284 points to psx_behavior_subopcode_dispatch (0x80027ecc),
  • subop table entry 49 at 0x800636d4 points to psx_level_gate_slot_dispatch_from_action_record (0x800214ac).

Best current candidate producer context

Strongest current candidate is still the type-state behavior program record lane:

  1. simple-record object instance in late final-mission family (map_id_to_gate_slot == 0x0f)
  2. component program emits opcode 54
  3. subop 49 frame resolves to sink 0x800214ac
  4. sink record bytes resolve to tuple (slot,arg1,arg2)=(0x0f,0x0a,0x04)

This is now stronger than the old "high opcode not proven active" read, because that old read depended on misinterpreting 0x80026710 as opcode clamp.

Authored-static vs runtime-remapped read (updated)

Current strongest classification remains:

  1. authored-static source lane at the behavior program record level,
  2. with optional runtime remap transport via arg-mask/index resolution in psx_object_behavior_opcode_dispatch.

Reason: arg resolver can pass direct pointers to record words (static bytes) or map arg words through slot-index tables (base + index*4) when mask bits are set. Exact mask choice of the emitting shipped record remains open.

Live Ghidra changes applied in this pass

Decompiler comments updated:

  • 0x80026710 (arg-count guard correction; opcode still independent)
  • 0x8002685c (opcode table index semantics + entry 54 viability)
  • 0x80027f0c (subop dispatch viability depends on authored records, not guard exclusion)

No renames were applied in this pass.

Synthesis-only conservative-stabilization pass (2026-04-12 live MCP)

This pass was synthesis-only: no new lane expansion, only decision cleanup on what to stabilize in live naming/comments from the callback/countdown/progression clue set.

Updated concise natural-event synthesis

  1. Natural JL-9 arm remains a timing-sensitive in-level event lane, not a passcode-decoder-only effect.
  2. 0x8002745c and 0x80027548 remain indirect callback-table targets (no recovered direct callers), so they are transition/progression context, not standalone proven tuple emitters.
  3. 0x80020794 remains world-frame countdown/control flow with map-54 boundary split (<=54 -> 0x1a, >54 -> 0x1b) and no recovered direct edge into slot-dispatch sink 0x800214ac.
  4. Best current miss model is unchanged: late transition/progression can consume the active window before slot-0x0f tuple (0x0a,0x04) reaches the arm write.

Live Ghidra changes applied in this pass

Decompiler comments were normalized to conservative evidence wording:

  • 0x8002745c (psx_control_callback_apply_level_preset_or_resume): indirect callback-table target + branch split; explicitly marked not a standalone proven direct slot-0x0f feeder.
  • 0x80027548 (psx_control_callback_apply_progression_target_level): indirect callback-table target + progression-state timing lane; explicitly marked as non-proof of tuple emission by itself.
  • 0x80020794 (psx_control_event_countdown_transition_tick): world-frame countdown/control lane + explicit no-direct-edge statement to sink 0x800214ac.

No new renames were applied in this pass.

Names intentionally left conservative

  1. No tuple-specific rename for 0x8002745c (insufficient direct feeder proof).
  2. No deterministic gate-arm rename for 0x80027548 (supports timing influence, not guaranteed tuple production).
  3. 0x800230e4 remains psx_control_event_slot0f_handler (slot-family scope retained; no narrowing to a single branch writer name).

Progression latch timing pass (2026-04-12 live MCP)

This pass revisited psx_control_callback_apply_progression_target_level (0x80027548) to decide whether map 54 -> 55 progression can preempt natural slot-0x0f tuple arm (0x0a,0x04).

Strongest new timing implication

  1. 0x80027548 reads psx_map_progression_table[current_map_id] and writes it through psx_level_session_set_next_map_id (0x8002ba84, renamed this pass).
  2. psx_level_session_set_next_map_id only stages deferred next-map latch DAT_800678d0; it does not immediately change current_map_id.
  3. psx_level_session_loop commits current_map_id <- DAT_800678d0 at rollover (0x80031edc) after inner world-frame loop exit.
  4. Practical consequence: this is not a same-tick overwrite race, but it still creates a preemption window if transition logic exits the map-54 loop before slot-0x0f tuple branch (0x0a,0x04) is emitted.

54->55 miss-window read (updated)

  1. 54 -> 55 remains the most likely natural miss window.
  2. The key mechanism is deferred rollover ordering, not direct tuple clobber.
  3. Countdown/control map split at 0x800208f0 (<=54 -> 0x1a, >54 -> 0x1b) plus staged progression target explains why natural tuple opportunity can disappear at the boundary.

Live Ghidra artifacts applied in this pass

Renames:

  • 0x8002ba84 -> psx_level_session_set_next_map_id

Decompiler comments:

  • 0x80027560
  • 0x8002ba84
  • 0x80031edc
  • 0x800208f0

Countdown-vs-slot frame-order closure pass (2026-04-12 live MCP)

This pass targeted one question only: whether the map-54 countdown split around 0x800208f0 can plausibly suppress or precede natural slot-0x0f arm tuple (0x0a,0x04).

Strongest causality clue recovered

Recovered world-frame order is now explicit in disassembly/decompile:

  1. 0x8002b830: psx_control_event_countdown_transition_tick
  2. 0x8002b844: psx_run_live_object_behavior_callbacks
  3. 0x8002b864: psx_update_motion_and_nearby_interactions
  4. 0x8002b86c: psx_flush_deferred_control_queue

So countdown runs earlier in frame than the later behavior/deferred-control lanes that can eventually reach slot dispatch and slot-0x0f arm sink 0x800232f0.

Map-54 split relevance and branch read

  1. At 0x800208f0, countdown terminal handoff selects mode by map boundary:
  • current_map_id <= 54 -> 0x1a
  • current_map_id > 54 -> 0x1b
  1. At 0x80020900, countdown immediately applies psx_control_set_mode_and_reset_runtime_flags, writing shared control globals (DAT_800675e4, DAT_80067379).
  2. Slot-0x0f arm branch (0x0a,0x04) in psx_control_event_slot0f_handler also writes the same control-mode family before/with gate write.

Updated relevance ranking

  1. Boundary timing is most relevant (strongest): early countdown split+mode write can narrow later slot-event windows in the same/next frame.
  2. Failure-side suppression is plausible but secondary: no direct countdown->slot call edge was recovered, but shared mode-state writes can still preempt tuple opportunity.
  3. Pure countdown-success branch is weakest: no recovered success-path edge deterministically forcing tuple (0x0f,0x0a,0x04).

Live Ghidra artifacts applied in this pass

No renames in this pass.

Decompiler comments added:

  • 0x8002b830: frame-order anchor (countdown before behavior/deferred lanes)
  • 0x80020900: boundary handoff writes shared mode/state before later slot lanes
  • 0x800232f0: slot-0x0f arm sink is later lane than countdown tick

Slot-0x0f subcase taxonomy pass (2026-04-12 live MCP)

This pass classified psx_control_event_slot0f_handler (0x800230e4) subcases beyond tuple (0x0a,0x04) to narrow natural JL-9 semantics without overfitting a single folklore path.

Compact taxonomy (slot 0x0f family)

  1. param2=0x0a, param3=0x01/0x02/0x03
  • Transition/objective-state setup (not direct reward grant).
  • Evidence: each branch writes shared control mode/timer state (DAT_80067340/44), plays a CD-XA cue (0x1a/0x1b/0x1d via 0x80049014), and clears per-event runtime latches.
  • param3=0x03 additionally sets DAT_80067354=1.
  1. param2=0x0a, param3=0x2e
  • Opcode-stream transition/reset lane.
  • Evidence: switches opcode stream index (psx_control_assign_opcode_stream_by_index(...,1)) and performs the same latch-clear pattern.
  1. param2=0x01, param3=0x01
  • Countdown/objective-pressure arm lane.
  • Evidence: writes DAT_8006734d/4e/4f, which are read by psx_control_event_countdown_transition_tick and psx_draw_clock_digits_overlay.
  1. param2=0x04, param3=0x01
  • Per-level message/objective-notice lane.
  • Evidence: loads text from DAT_8006754c + index, sets timer/control globals, and drives shared countdown-style object iteration side effects.
  1. param2=0x06, param3=0x42
  • Type-gated control transition lane.
  • Evidence: writes only global mode latches (DAT_800675e4, DAT_80067379, DAT_80067350) and reset helpers; no constructor/resource-bind call.
  1. param2=0x0a, param3=0x04 (anchor case)
  • Eligibility arm latch for hidden follow-up, not immediate reward.
  • Evidence: only this subcase writes psx_debug_extra_channel_gate (0x800232f0) under psx_hidden_passcode_flag==0 && psx_level_runtime_header_state==3.

Implication for (0x0a,0x04)

The surrounding slot-0x0f family is dominated by transition/countdown/objective-state control lanes and messaging, not direct reward payout handlers. That makes (0x0a,0x04) best interpreted as a late objective-state eligibility arm inside that control family, rather than a standalone reward/failure terminal event.

Live Ghidra artifacts applied in this pass

Renames:

  • 0x80049014 -> psx_audio_cdxa_select_and_play_cue

Decompiler comments added:

  • 0x80023154 (slot0f 0x0a/1 transition setup)
  • 0x800231b0 (slot0f 0x0a/2 countdown/objective-state setup)
  • 0x8002321c (slot0f 0x0a/3 sibling objective-state branch)
  • 0x80023334 (slot0f 0x0a/0x2e opcode-stream reset lane)
  • 0x80023390 (slot0f 0x01/1 countdown-overlay/objective-pressure lane)
  • 0x800236d8 (slot0f 0x04/1 level-message objective cue lane)
  • 0x800237cc (slot0f 0x06/0x42 type-gated control transition lane)

Transition callback provenance pass (2026-04-11 live MCP)

This pass traced the newly recovered transition callbacks at 0x8002745c and 0x80027548 to determine provenance, state effects, and impact on the slot-0x0f tuple lane.

Provenance (best current)

  1. Neither callback has a recovered direct caller xref (get_callers returns none for both entries).
  2. Both are data-referenced from a contiguous function-pointer region near 0x800641f0..0x80064220:
  • 0x80064200 -> 0x8002745c
  • 0x80064210 -> 0x80027548
  1. Current best classification is therefore indirect callback-table dispatch from an unresolved transition/control dispatcher, not direct static callsites.

State effects recovered

  1. 0x8002745c (psx_control_callback_apply_level_preset_or_resume):
  • if DAT_80078a14 == 0, it calls psx_control_event_apply_level_channel_preset (0x80020f7c) then psx_control_reset_runtime_flag_67780.
  • else it takes an alternate resume lane (FUN_80044074) and sets DAT_800673c4 = 1.
  1. 0x80027548 (psx_control_callback_apply_progression_target_level):
  • computes next = psx_map_progression_table[current_map_id] and applies it via FUN_8002ba84.

Slot-0x0f tuple carry/suppress read

  1. 0x8002745c can carry tuple opportunity when it takes the DAT_80078a14 == 0 branch because that branch executes 0x80020f7c (the known level/channel apply helper in this late control lane).
  2. The alternate branch in 0x8002745c can suppress or defer tuple opportunity for that tick because it skips 0x80020f7c and takes resume/state handling instead.
  3. 0x80027548 increases timing sensitivity: it advances map progression before later control-event handling, so slot-0x0f tuple (0x0a,0x04) only carries if emitted after/within the progressed state window; otherwise progression can outrun the arm branch.
  4. For map 54 -> 55, this is not a simple family exit (both are still slot-0x0f family), but it is a real state-transition step that can change event ordering and reduce deterministic tuple timing.

Live Ghidra artifacts applied in this pass

Comments added:

  • 0x8002745c: table-dispatch provenance + carry/suppress branch semantics.
  • 0x80027548: progression callback provenance + timing-sensitive tuple implication.
  • 0x80064200: callback-table-region note for 0x8002745c entry.
  • 0x80064210: callback-table-region note for 0x80027548 entry.

No additional renames were applied in this pass (existing callback names remained conservative and evidence-backed).

Multi-map last-mission chain check (2026-04-11 live MCP)

This pass tested the specific hunch that the last mission is split across multiple maps and that this split explains why the JL-9 gate-arm family is level-grouped (54/55/56/57/58) instead of single-map.

Fresh table evidence (live bytes)

Recovered directly from active SLUS_002.68 memory tables:

  1. Selector anchor into the family:
  • psx_selector_to_map_id_table[0x0f] at 0x80063e63 is 0x36 (map 54).
  1. Gate-slot family closure for the target chain:
  • psx_map_id_to_gate_slot_table[54..58] at 0x80063e9e..0x80063ea2 is 0x0f,0x0f,0x0f,0x0f,0x0f.
  1. Progression chain closure:
  • psx_map_progression_table[54..58] at 0x80063ee2..0x80063ee6 is 0x37,0x38,0x39,0x3a,0xff (55,56,57,58,terminal).

Net result: this is a concrete contiguous progression chain that stays inside one gate-slot family (0x0f) until terminal progression marker 0xff.

Control/transition helper evidence for split-flow behavior

  1. psx_control_event_countdown_transition_tick (0x80020794) has an explicit map boundary at 0x800208f0:
  • current_map_id <= 54 uses mode 0x1a
  • current_map_id > 54 uses mode 0x1b
  1. psx_control_event_slot0e_handler has a late feeder branch at 0x80023074 that writes selector 0x0f then calls psx_control_event_apply_level_channel_preset.
  2. psx_control_event_apply_level_channel_preset reads both:
  • psx_map_progression_table[current_map_id] (0x80020fa4)
  • psx_map_id_to_gate_slot_table[current_map_id] (0x80020fbc)

This supports a staged final-mission flow where late transition/control logic can traverse 54..58 while remaining in the same slot-0x0f gate family.

Best reconstruction (current)

  1. Normal passcode selector path anchors entry at map 54 (selector 0x0f -> map 54).
  2. Late control/transition logic advances through progression chain 54 -> 55 -> 56 -> 57 -> 58.
  3. Across that chain, gate dispatch stays in slot family 0x0f.
  4. Terminal progression marker at map 58 (0xff) indicates end-of-chain behavior rather than another ordinary progression handoff.

JL-9 implication update

This strengthens, not weakens, the level 54 natural-host read:

  1. 54 remains the only member with direct selector anchor from the recovered normal passcode path.
  2. 55..58 are now better interpreted as downstream split-phase transition maps in the same family.
  3. The family-wide slot-0x0f mapping now looks like deliberate multi-map mission staging, which explains why JL-9 gate logic is host-family scoped while still leaving 54 as the strongest reproducible natural entry.

Live Ghidra artifacts applied in this pass

Disassembly comments added:

  • 0x80063e63 (selector 0x0f -> map 54 anchor)
  • 0x80063e9e (map 54 slot-0x0f family start)
  • 0x80063ea2 (map 58 slot-0x0f terminal-edge note)
  • 0x80063ee2 (progression 54 -> 55 chain start)
  • 0x80063ee6 (progression 58 -> 0xff terminal marker)
  • 0x800208f0 (map-54 boundary split 0x1a/0x1b)
  • 0x80023074 (slot-0x0e late feeder back into selector 0x0f family)
  • 0x80020fa4 (progression-table read and 54..58 staged flow note)

Level-54 boundary clue pass (2026-04-11 live MCP)

This pass focused on one narrow question: why natural MFM4 can still miss even when map 54 remains the best host anchor.

Strongest level-54-specific clue

psx_control_event_countdown_transition_tick (0x80020794) has an explicit map boundary at 0x36 (54):

  • at terminal countdown handoff, it chooses control mode code 0x1a when current_map_id <= 0x36
  • and chooses 0x1b when current_map_id > 0x36

This is a concrete level-54 split in late control flow, not a generic family-level observation.

Supporting progression/control evidence recovered in the same pass:

  • psx_control_callback_apply_progression_target_level (0x80027548) applies psx_map_progression_table[current_map_id] via FUN_8002ba84
  • for map 54, that progression target is 55
  • psx_level_gate_slot05_handler (0x80021fac) has tuple branch (param2=0x0a,param3=0x28) that calls psx_control_event_apply_level_channel_preset and reset helpers, giving an optional late state-advance lane before slot 0x0f (0x0a,0x04) arm may fire

Practical interpretation for natural MFM4 failures

Current best read is now sharper:

  1. MFM4 still correctly primes the strongest known host (selector 0x0f -> map 54, runtime_header_state=3).
  2. Failure is most likely a timing/optional-path miss inside late control-event progression around the map-54 boundary split, not wrong-host decode.
  3. The rare miss model is: a transition/control branch (including map-54-specific 0x1a lane and optional slot05 branching) can advance or reroute state before slot 0x0f (0x0a,0x04) writes psx_debug_extra_channel_gate.

Live Ghidra artifacts applied in this pass

Renames:

  • 0x80020794 -> psx_control_event_countdown_transition_tick
  • 0x800205e8 -> psx_control_event_apply_countdown_step
  • 0x80020d54 -> psx_control_set_mode_and_reset_runtime_flags
  • created 0x8002745c -> psx_control_callback_apply_level_preset_or_resume
  • created 0x80027548 -> psx_control_callback_apply_progression_target_level

Comments:

  • 0x800208f0: level-54 boundary (<=54 -> 0x1a, >54 -> 0x1b) and timing implication
  • 0x80022068: slot05 (0x0a,0x28) optional late-event/preset lane note
  • 0x80027560: progression callback note (next = map_progression_table[current_map_id])

Slot-handler sibling recovery around table 0x800640a0 (2026-04-11 live pass)

Focused scope for this pass was the still-raw slot-handler siblings referenced by psx_level_gate_slot_handler_table (0x800640a0), with emphasis on whether any 0x0a sibling branch competes with or narrows tuple (0x0a,0x04) in slot 0x0f.

Recovered sibling handler roles (table entries)

Newly created and named from table boundaries (address range split by next table entry):

  • 0x800215fc -> psx_level_gate_slot01_handler
  • 0x80021810 -> psx_level_gate_slot02_handler
  • 0x800219e4 -> psx_level_gate_slot03_handler
  • 0x80021fac -> psx_level_gate_slot05_handler
  • 0x80022214 -> psx_level_gate_slot06_handler
  • 0x800222e8 -> psx_level_gate_slot07_handler
  • 0x800223cc -> psx_level_gate_slot08_handler
  • 0x800226e0 -> psx_level_gate_slot09_handler
  • 0x800227ac -> psx_level_gate_slot0a_handler
  • 0x80022b50 -> psx_level_gate_slot0c_handler
  • 0x80023854 -> psx_level_gate_slot00_handler
  • 0x80023af0 -> psx_level_gate_slot10_return_true

Existing slot siblings kept/updated:

  • 0x80022c6c = psx_control_event_slot0d_handler
  • 0x80022ea8 = psx_control_event_slot0e_handler
  • 0x800230e4 = psx_control_event_slot0f_handler
  • 0x80022940 renamed to psx_level_gate_slot0b_control_pair_handler
  • table entry still points to 0x8002293c (first prologue instruction), now preserved via comment.

New interpretation of the 0x0a subcase family

Current strongest narrowing is now sibling-explicit across slots 0x0a..0x0f:

  1. Slot 0x0a (0x800227ac) has (0x0a,0x01..0x03) control/timer branches and does not write psx_debug_extra_channel_gate.
  2. Slot 0x0b (0x80022940) (0x0a,0x02) sets policy bit DAT_80078a88 |= 0x0400, but is not a gate-byte writer.
  3. Slot 0x0c (0x80022b50) 0x0a family is control/message gated by DAT_80078a88 & 0x200, with no recovered gate-byte write.
  4. Slot 0x0d (0x80022c6c) (0x0a,0x02) is mission-complete passcode text lane (quad index 0x0e).
  5. Slot 0x0e (0x80022ea8) (0x0a,0x01) is mission-complete passcode text lane (quad index 0x0f); (0x0a,0x06) is late selector/apply transition.
  6. Slot 0x0f (0x800230e4) (0x0a,0x04) remains the only recovered sibling in this family that can arm psx_debug_extra_channel_gate (write at 0x800232f0) under non-hidden + header-state-3 predicates.

So (0x0a,0x04) is now narrower than before: it is not just “one 0x0a case in slot0f”, it is the only recovered 0x0a-family sibling across adjacent slot handlers that actually sets the JL-9 gate byte.

0x0a control-family semantic closure (2026-04-11 live pass)

This pass was restricted to the late control family around:

  • 0x80022c6c (psx_control_event_slot0d_handler)
  • 0x80022ea8 (psx_control_event_slot0e_handler)
  • 0x800230e4 (renamed this pass to psx_control_event_slot0f_handler)
  • 0x80020f7c (psx_control_event_apply_level_channel_preset)

Best semantic classification for (0x0a,0x04)

Current best read is control-event eligibility arm (pre-hidden gate latch), not a reward payload by itself.

Evidence:

  1. (0x0a,0x04) is in slot 0x0f handler and writes psx_debug_extra_channel_gate only under:
  • psx_hidden_passcode_flag == 0
  • psx_level_runtime_header_state == 3
  1. The write is a single byte latch (sb) at 0x800232f0, then consumed later by the hidden/debug grant lane at 0x8002fff4.
  2. Sibling 0x0a cases in slot 0x0d/0x0e are mission-complete text/transition/control setup branches, which places (0x0a,0x04) inside a broader control progression family rather than a standalone "grant now" event.

Sibling-case comparison table (param_2 == 0x0a)

Handler slot Address Subcase (param_3) Strongest semantics Direct effect
0x0d 0x80022c6c 0x02 mission-complete passcode text branch generates encoded quad index 0x0e, writes congratulations text
0x0e 0x80022ea8 0x01 mission-complete passcode text branch generates encoded quad index 0x0f, writes congratulations text
0x0e 0x80022ea8 0x02..0x04 transition/setup control branches mode/timer/runtime control setup
0x0e 0x80022ea8 0x06 late selector/channel transition sets selector 0x0f, calls psx_control_event_apply_level_channel_preset and psx_passcode_apply_mission_selector_and_stage
0x0f 0x800230e4 0x01..0x03 sibling control/event setup runtime/control side effects without gate-byte arm
0x0f 0x800230e4 0x04 pre-hidden eligibility arm latch conditional write psx_debug_extra_channel_gate = 1 at 0x800232f0

Net: (0x0a,0x04) is best classified as a control-arm/preset-eligibility latch for the later hidden completion path, not as direct reward completion.

Sink-side feeder closure (2026-04-11 live pass)

This pass was restricted to the sink-side feeder and argument sourcing around 0x800214ac..0x800215f8.

Recovered action-record dispatch structure

Recovered function body is now promoted in live Ghidra as:

  • 0x800214ac -> psx_level_gate_slot_dispatch_from_action_record

Observed layout used by this dispatcher:

  • record+0x00 -> pointer to slot byte (slot = *(*(record+0x00)))
  • record+0x08 -> pointer to arg1 byte (arg1 = *(*(record+0x08)))
  • record+0x0c -> pointer to arg2 byte (arg2 = *(*(record+0x0c)))

Dispatch mechanics:

  • level gate compare uses psx_map_id_to_gate_slot_table[current_map_id] (0x80063e68)
  • indirect call uses psx_level_gate_slot_handler_table[slot] (0x800640a0)
  • slot 0x0f entry points to 0x800230e4 (psx_set_debug_extra_channel_gate) at table address 0x800640dc

JL-9 gate-arm tuple remains explicit and unchanged:

  • slot 0x0f
  • arg1 = 0x0a
  • arg2 = 0x04

which reaches the gate-byte write branch at 0x800232f0.

Upstream producer status (what is proven)

Proven table topology into the sink feeder:

  • psx_behavior_opcode_handler_table[54] = 0x80027ecc (psx_behavior_subopcode_dispatch) at 0x80064284
  • psx_behavior_subop_handler_table[49] = 0x800214ac (psx_level_gate_slot_dispatch_from_action_record) at 0x800636d4

Current proof boundary remains the same:

  • known gameplay caller lane into psx_object_behavior_opcode_dispatch still enforces (opcode_word-1) < 0x0a at 0x80026710
  • therefore 54 -> 49 -> 0x800214ac is retained as proven table topology, not yet proven active on the currently recovered caller path

Host-level family closure for natural JL-9 gate-arm (2026-04-11 live pass)

Scope of this pass was restricted to the host-side family {54,55,56,57,58,82} with direct MCP evidence for:

  • selector mapping (psx_selector_to_map_id_table, 0x80063e54)
  • level/map gate-slot mapping (psx_map_id_to_gate_slot_table, 0x80063e68)
  • progression/completion transitions (psx_map_progression_table, 0x80063eac, plus FUN_80020f7c callers)

Exact mapping closure used for ranking

  1. Published normal-code selector closure still anchors on selector 0x0f:
  • psx_selector_to_map_id_table[0x0f] = 0x36 (map id 54)
  1. Slot-family closure for natural gate-arm dispatch:
  • psx_map_id_to_gate_slot_table[54] = 0x0f
  • psx_map_id_to_gate_slot_table[55] = 0x0f
  • psx_map_id_to_gate_slot_table[56] = 0x0f
  • psx_map_id_to_gate_slot_table[57] = 0x0f
  • psx_map_id_to_gate_slot_table[58] = 0x0f
  • psx_map_id_to_gate_slot_table[82] = 0x0f
  1. Reciprocal passcode-validation behavior in caller flow around 0x80034d60..0x80034d7c remains strongest for map 54:
  • map id 54 round-trips through slot 0x0f -> map 54 by reverse lookup
  • sibling family members (55..58,82) share slot 0x0f but do not have the same direct selector round-trip anchor in recovered normal passcode tables

Late objective/completion hosting evidence

FUN_80020f7c remains the strongest late transition hub in this lane. Live callers include:

  • 0x80022068
  • 0x80022e58
  • 0x80023080
  • 0x8002748c

and the helper itself reads both:

  • psx_map_progression_table[current_map_id] (0x80020fa4)
  • psx_map_id_to_gate_slot_table[current_map_id] (0x80020fbc)

Progression bytes for the host family are now explicit:

  • map 54 -> 0x37 (55)
  • map 55 -> 0x38 (56)
  • map 56 -> 0x39 (57)
  • map 57 -> 0x3a (58)
  • map 58 -> 0xff (terminal marker in this table)
  • map 82 -> 0x53 (83)

This keeps a completion-timing explanation live: transition-heavy late events can move execution across the same slot-0x0f family, but not all members are equally stable for deterministic player replication.

Ranked host levels/maps (current best)

  1. Map/level 54 (best host)
  • only family member directly anchored by recovered normal selector path (0x0f -> 54)
  • satisfies both prime-side and slot-family-side constraints in one static route
  1. Map/level 55
  • immediate progression successor of 54 and still in slot-0x0f family
  • plausible if gate-arm event is late-transition-bound rather than early-in-map
  1. Map/level 56
  • same slot family, but one transition further away from direct passcode anchor
  1. Map/level 57
  • same slot family, lower reproducibility due to additional transition depth
  1. Map/level 82
  • valid slot-family host (0x0f) but weak for this specific user scenario because no direct published selector anchor was recovered
  1. Map/level 58
  • slot-family valid, but progression entry marks terminal (0xff) and is therefore most likely to race/exit through completion paths rather than offer stable pre-hidden gate-arm timing

Best current explanation for failed natural MFM4

After the failed natural MFM4 trial, the best-supported explanation remains event-host/timing failure, not decode failure:

  1. MFM4 still fits the strongest static prime (runtime_header_state==3, selector family leading to map 54).
  2. The missing piece is still the in-level/control dispatch that must reach slot 0x0f with tuple (0x0a,0x04) before hidden-mode trigger.
  3. Completion/transition hubs (FUN_80020f7c callers) can move state through family members and potentially past the required gate-arm moment.
  4. So a natural run can fail even with correct passcode if:
  • the specific tuple event never fired,
  • it fired outside the required hidden-flag polarity window,
  • or progression/completion moved the run into a less stable timing state before hidden-input trigger.

Net result for this pass: map 54 remains the best host candidate, but MFM4 alone is not expected to be sufficient without the concrete in-level event timing.

Late-objective / mission-complete clue pass (2026-04-11, live MCP)

Focused this pass only on concrete late-objective and reward-event clues that can naturally arm the JL-9 gate path (psx_debug_extra_channel_gate), with emphasis on mission-complete text handlers, transition siblings, and uncommon branch outcomes.

Hard evidence (high confidence)

  1. Mission-complete congratulations strings are anchored at:
  • 0x80063f10: congrats template with next-passcode tail.
  • 0x80063f74: shorter congrats variant.
  1. Two adjacent control-event handlers are concrete mission-complete/passcode writers:
  • 0x80022c6c (DAT_800640a0[0x0d]): slot-0x0d branch generates encoded passcode index 0x0e and writes congrats text.
  • 0x80022ea8 (DAT_800640a0[0x0e]): slot-0x0e branch generates encoded passcode index 0x0f and writes congrats text.
  1. Slot-0x0e has a concrete late-transition branch that force-loads selector 0x0f:
  • at 0x80023040 (param2=0x0a,param3=6 branch), code sets DAT_80078a8c=0x0f and calls psx_passcode_apply_mission_selector_and_stage.
  1. Selector/channel mapping remains decisive for natural gate-arm routing:
  • selector 0x0f -> level 0x36 (54) -> DAT_80063e68[level]=0x0f.
  • in sampled selector range, this is the only selector that maps directly into channel family 0x0f.
  1. Natural gate-arm write remains bounded to slot-0x0f case (param2=0x0a,param3=4):
  • write at 0x800232f0 sets DAT_8006739d only if psx_hidden_passcode_flag==0 and psx_level_runtime_header_state==3.

Text-display siblings and uncommon branch clues

  1. Slot-0x0f text-display sibling at 0x800236dc (param2=4,param3=1) calls ui_message_set_active_text(DAT_8006754c + idx).
  • This is resource-table driven, distinct from hardcoded congratulations templates.
  • It is a strong clue for optional scripted outcomes/messages tied to level-authored text resources.
  1. Slot-0x0e uncommon outcomes include:
  • param3=5: writes DAT_80078a10=2 (state-only branch).
  • param3=6: direct selector apply to 0x0f (late transition behavior, not ordinary text-only flow).

Strongest natural late-objective candidate events from this pass

  1. slot 0x0e mission-complete transition branch (0x80022ea8, param2=0x0a,param3=6) as the clearest upstream feeder into selector/channel 0x0f family.
  2. slot 0x0f event branch (0x800230e4, param2=0x0a,param3=4) as the proven natural JL-9 gate-arm writer under non-hidden + runtime-state-3 predicates.
  3. slot 0x0f text-resource branch (0x800236dc, param2=4,param3=1) as the strongest sibling clue for uncommon/optional scripted objective messaging that likely co-occurs with late transition states.

What is still speculation

  1. Exact player-visible authored event sequence inside the level (the concrete map script moment) that emits slot 0x0f with (0x0a,4) remains unclosed.
  2. Any claim that one specific congratulation screen alone implies gate-arm is still speculative; current proof is branch-level and table-level, not full authored mission-script closure.

0x0a family case-map closure around natural JL-9 gate arm (2026-04-11 live pass)

This pass was restricted to the control-event family around 0x80022c6c..0x80023390 with live MCP decompile/disassembly and conservative in-database naming.

Recovered sibling handlers and table roles

  1. Slot-handler table bytes at 0x800640d4/0x800640d8/0x800640dc confirm sibling entries:
  • slot 0x0d -> 0x80022c6c
  • slot 0x0e -> 0x80022ea8
  • slot 0x0f -> 0x800230e4 (psx_set_debug_extra_channel_gate)
  1. Sink dispatch block 0x800214ac..0x800215f8 remains the slot-gated tuple sink:
  • compare slot byte with DAT_80063e68[current_level]
  • if equal, call DAT_800640a0[slot] with tuple (param_2,param_3) loaded from record-byte pointers.

Concrete 0x0a family mapping (best current)

For these three sibling handlers, param_2 == 0x0a is the family selector and param_3 is the subcase index.

  1. Slot 0x0d handler (0x80022c6c):
  • (0x0a,0x02): generates passcode quad index 0x0e, writes encoded chars to 0x80063f6e..0x80063f71, shows congratulations text.
  1. Slot 0x0e handler (0x80022ea8):
  • (0x0a,0x01): same mission-complete passcode lane but quad index 0x0f.
  • (0x0a,0x02..0x04): mode/timer setup branches.
  • (0x0a,0x06): applies mission selector and stage.
  1. Slot 0x0f handler (0x800230e4):
  • (0x0a,0x01..0x04,0x2e) switch family.
  • critical branch (0x0a,0x04) at 0x800232f0: sets psx_debug_extra_channel_gate=1 only when psx_hidden_passcode_flag==0 and psx_level_runtime_header_state==3.

Interpretation of tuple (0x0a,0x04)

Strongest conservative interpretation is unchanged but now better bounded: this is the natural pre-hidden arm event branch in the slot-0x0f family, sibling to mission-complete/control subcases in slots 0x0d/0x0e/0x0f, and it is the direct writer path for the later hidden debug grant extra-lane check.

Practical consequence:

  1. (0x0a,0x04) is not an isolated special; it belongs to a coherent control-event sibling family.
  2. Its placement next to mission-complete style tuple branches strengthens the read that the missing natural trigger is an in-level authored control-event emission, not a separate passcode-screen operation.

Natural in-level event synthesis and conservative naming sweep (2026-04-11 live pass)

This pass was restricted to the natural in-level gate-arm event lane for JL-9, not the broader hidden/debug chain.

Event-only synthesis (current best conservative read)

  1. Natural arm still centers on in-level control/event dispatch into the slot-gated sink block at 0x800214ac..0x800215f8.
  2. Inside that sink, current-level family is checked against the slot index and then dispatches through slot-handler table entry slot 0x0f -> 0x800230e4 (psx_set_debug_extra_channel_gate).
  3. Actual gate write remains constrained to tuple branch (param_2 == 0x0a, param_3 == 0x04) at 0x800232f0, plus existing non-hidden/header-state predicates.
  4. Upstream behavior-opcode topology (opcode 54 -> subop 49 -> sink) is still structurally supported by table links, but active reachability from the only proven gameplay caller lane remains unproven because the recovered guard at 0x80026710 bounds the known lane to (opcode_word-1) < 0x0a.

Live Ghidra conservative rename/comment sweep applied

Renamed data/labels (only still-raw central entities):

  • 0x800640a0: PTR_LAB_800640a0 -> psx_level_gate_slot_handler_table
  • 0x800641ac: PTR_LAB_800641ac -> psx_behavior_opcode_handler_table
  • 0x80063610: PTR_LAB_80063610 -> psx_behavior_subop_handler_table
  • 0x800214ac: LAB_800214ac -> psx_level_gate_slot_dispatch_block_800214ac
  • 0x80027ecc: LAB_80027ecc -> psx_behavior_subop_dispatch_block_80027ecc

Added disassembly comments (evidence/uncertainty preserving):

  • 0x800215dc: level-family compare + slot-table indirect-call behavior (slot 0x0f sink relevance).
  • 0x800232f0: exact gate-arm tuple and predicate reminder.
  • 0x8002685c: opcode-table dispatch note plus proven caller-lane bound reminder.
  • 0x80027f0c: sub-op table dispatch note with explicit reachability uncertainty.
  • 0x800214ac: event-sink dispatch-role note.

Unresolved on purpose

  • Exact player-visible authored in-level event that naturally emits the (0x0f, 0x0a, 0x04) sink tuple.
  • Proof of an additional active caller/context that can reach high behavior-opcode entries beyond the known < 0x0a lane.
  • Any stronger semantic name for 0x80027ecc than conservative sub-op dispatch block labeling.

Event-only synthesis continuation (2026-04-11 live pass)

Scope of this continuation pass was intentionally narrow: keep sink-side natural-event naming durable without promoting unproven upstream producer semantics.

Updated concise synthesis (event-only)

  1. Natural JL-9 pre-hidden arm remains a slot-family control-event branch, not a passcode-screen branch.
  2. Sink path is unchanged and still strongest: psx_level_gate_slot_dispatch_from_action_record (0x800214ac) validates current-level family, then dispatches through psx_level_gate_slot_handler_table[slot].
  3. Slot 0x0f resolves to psx_control_event_slot0f_handler (0x800230e4), where tuple (0x0a,0x04) at 0x800232f0 conditionally writes psx_debug_extra_channel_gate only under hidden==0 && runtime_header_state==3.
  4. Upstream opcode 54 -> subop 49 -> sink remains table-valid topology, but active gameplay reachability is still unproven past the known caller lane bounded by (opcode_word-1) < 0x0a.

Exact live Ghidra artifacts changed in this continuation pass

Renamed function:

  • 0x800230e4: psx_set_debug_extra_channel_gate -> psx_control_event_slot0f_handler

Renamed table-entry labels:

  • 0x800640d4 -> psx_level_gate_slot_handler_slot0d_entry
  • 0x800640d8 -> psx_level_gate_slot_handler_slot0e_entry
  • 0x800640dc -> psx_level_gate_slot_handler_slot0f_entry

Added/updated comments:

  • 0x800230e4: slot-0x0f family role comment; gate write classified as one subcase.
  • 0x800232f0: exact natural gate-arm tuple + predicate comment.
  • 0x800640dc: slot-0x0f table-entry comment with tuple anchor.
  • 0x80064284: opcode-table entry 54 comment preserving topology-vs-reachability split.
  • 0x800636d4: subop-table entry 49 comment preserving topology-vs-reachability split.

Deliberately unresolved / refused names

  1. Refused rename: psx_behavior_subopcode_dispatch (0x80027ecc) to any stronger event-producer name.
  • Why refused: no direct recovered caller currently proves active high-index opcode reachability on gameplay path.
  1. Refused rename: psx_level_gate_slot_dispatch_from_action_record (0x800214ac) to mission-specific or JL-9-specific wording.
  • Why refused: sink dispatch behavior is generic slot-family machinery beyond JL-9.

Published mission code sweep closure (2026-04-11 live pass)

Focused live pass on the user-supplied ordinary PSX mission code table was used to answer one narrow question: does any published non-hidden mission code already satisfy the static JL-9 preconditions, or do they all fail before the hidden/debug stage?

Exact decode-space closure

  1. Ordinary passcode rows are decode indices i = 0x00..0x0e.
  2. Special passcodes are the next three rows:
  • i = 0x0f => ?RTN family
  • i = 0x10 => ?0SR family
  • i = 0x11 => ?QQQ family
  1. For ordinary rows, selector space is offset by one:
  • ordinary decoded row i returns selector s = i + 1
  1. First-character difficulty only affects psx_level_runtime_header_state; it does not choose the selector/current-level lane.

Strongest published-code candidate

MFM4 is now the strongest ordinary published JL-9 setup candidate.

Reasoning:

  1. Suffix FM4 matches ordinary decode row i = 0x0e.
  2. First char M yields accepted delta 3, so normal decode writes psx_level_runtime_header_state = 3.
  3. Ordinary row i = 0x0e returns selector s = 0x0f.
  4. Live table bytes then map:
  • DAT_80063e54[0x0f] = 0x36 (54 decimal)
  • DAT_80063e68[54] = 0x0f
  1. Therefore MFM4 is the only currently recovered published mission code that statically satisfies both known preconditions at once:
  • non-hidden prime with runtime_header_state == 3
  • current-level family that can reach slot-0x0f dispatch

Strong negatives from the same sweep

  • LRTN / MRTN / PRTN are not ordinary mission primes; they are the special ?RTN family and clear psx_level_runtime_header_state to 0.
  • the older contradiction in this investigation came from mixing decode-row space (i) with returned selector space (s); MFM4 works because ordinary i = 0x0e still returns selector 0x0f.
  • no published mission code currently closes the full JL-9 route by itself; even MFM4 still needs the in-level gate-arm event and then the later hidden/input trigger.
  • the user-supplied level-11 strings containing O should be treated cautiously as written because O is not present in the recovered passcode alphabet (BCDFGHJKLMNPQRSTVWXZ0123456789).

Practical consequence

Current best actionable static route is now:

  1. Enter MFM4 as the ordinary prime.
  2. In the same running session, hit the in-level scripted/control event that emits dispatch tuple (0x0f,0x0a,0x04) and arms psx_debug_extra_channel_gate.
  3. Enter hidden L0SR / ?0SR.
  4. Press R1 + Circle.

This is still not a fully closed player recipe because step 2 remains the missing concrete event, but it is now the strongest evidence-backed published-code path rather than a generic “some normal code with state 3” placeholder.

Hard-clear theory check and manual gate-poke closure (2026-04-11 live pass)

Two follow-up questions were tested after MFM4 was isolated as the strongest published prime candidate:

  1. is the missing in-level trigger actually tied to beating the game, especially on hard?
  2. if not, is manual arming of the known gate byte enough to make hidden L0SR plus the trigger input unlock JL-9?

Hard-clear theory verdict

Current evidence keeps the "beat the game on hard, then enter L0SR" theory in the weak-to-medium bucket, not the lead explanation.

What was actually recovered:

  1. congratulation/completion text paths do exist near unnamed code around 0x800204fc, 0x80022d20, and 0x80022f68.
  2. those paths are plausible mission-complete / transition / UI handlers, but no direct write to psx_debug_extra_channel_gate (0x8006739d) was recovered from them.
  3. no better persistent JL-9-specific latch was recovered than the already-known gate byte itself.
  4. the strongest executable-backed model still points to an in-level scripted/event dispatch lane, not a post-credits reward lane.

So the current read is narrower than folklore but not fully closed:

  • MFM4 plausibly matters because it is the only published prime that sets runtime_header_state = 3 and reaches the slot-0x0f gate family.
  • but beating the final mission on hard is not yet proven to be the missing arm event.
  • the unresolved gap remains the same concrete in-level producer for dispatch tuple (0x0f,0x0a,0x04).

Manual gate-poke closure

The practical emulator test is now strong enough to state directly.

  1. psx_debug_extra_channel_gate is runtime byte 0x8006739d.
  2. writer at 0x800232f0 uses sb, so storage is byte-wide.
  3. reader at 0x8002fff4 uses lbu then branches on zero, so the gate is checked as nonzero, not as one exact magic literal.
  4. no second direct writer or clear for 0x8006739d has been recovered in the inspected session/load/menu paths.

Practical manual test sequence

The strongest current poke test is:

  1. set byte 0x8006739d = 0x01
  2. enter hidden L0SR / ?0SR
  3. press R1 + Circle

Expected result if the trigger path executes normally:

  • hidden input path reaches psx_debug_grant_weapon_channels_and_ammo
  • late read at 0x8002fff4 sees nonzero gate byte
  • extra unlock path at 0x80030004 runs for channel/index 0x0d (JL-9 lane)

Practical caveats

  • the poke bypasses the natural writer-side predicates (hidden==0, runtime_header_state==3), so it does not validate the true in-level event by itself.
  • hidden mode still has to be active when the input trigger is pressed.
  • if the emulator uses uncached/physical mirrors, the same byte may appear as 0x0006739d depending on the UI, but the logical KSEG0 runtime address is 0x8006739d.
  • if a manual test fails with gate byte set, the next most likely cause is that hidden mode timed out or the input chord did not decode to 0x1e.

User-validated downstream closure

User emulator verification now confirms the downstream half directly:

  1. set main-memory byte 0x8006739d = 0x01
  2. enter L0SR
  3. start gameplay and press R1 + Circle
  4. result: JL-9 appears in inventory next to JL-2

Practical meaning of that success:

  • this validates the late hidden/input grant half of the model, not the natural writer path.
  • the poke directly pre-satisfies the nonzero check at 0x8002fff4, so the test does not prove that MFM4 or the natural in-level gate-arm event occurred.
  • among those two upstream elements, the more direct thing bypassed is the in-level trigger itself, because the poke replaces the writer result (psx_debug_extra_channel_gate = 1) rather than recreating the natural writer predicates that would normally produce it.
  • MFM4 is therefore best read now as the strongest natural prime candidate, not as a required part of the forced test.

User experiment follow-up (2026-04-11)

Additional emulator trials now tighten the natural-versus-forced split further.

  1. Natural MFM4 trial:
  • user entered MFM4, allowed the level to load, then later returned to menu and attempted the hidden/input phase.
  • result: no JL-9; only ordinary JL-2 was present.
  • practical meaning: current evidence no longer supports MFM4 by itself as a sufficient natural route.
  1. Forced-gate control with non-hard final-level code:
  • user entered JFM4 (final map on easy), returned to menu, set 0x8006739d = 0x01, entered L0SR, started a level, and pressed R1 + Circle.
  • result: JL-9 still appeared.
  • practical meaning: the forced downstream route does not depend on MFM4 specifically; manual gate arm is enough even when the prime code is not the hard-difficulty candidate.

Current best interpretation after these trials:

  • MFM4 remains the strongest natural prime candidate because it matches the writer-side predicates statically.
  • but it is no longer the lead bottleneck for the overall mystery.
  • the missing natural in-level event is now the dominant unknown.

Deferred user experiments

Keep these queued for later follow-up:

  1. Experiment 2: hard-clear / beat-the-game test
  2. Experiment 4: compare MFM4 against another header-state-3 code under matched in-level actions
  3. Experiment 5: hold map/event family constant and vary only the suspected scripted event
  4. Experiment 6: strict ordering test (event -> L0SR -> trigger versus L0SR -> event -> trigger)

NRTN / ?RTN passcode path closure (2026-04-11 live pass)

Focused live MCP pass on active SLUS_002.68 for:

  • psx_passcode_decode_to_mission_selector (0x8003ec8c)
  • psx_passcode_apply_mission_selector_and_stage (0x80021138)
  • caller block around 0x80034c14..0x80034ddc

Exact decode and selector behavior

  1. The special ?RTN row is decode index i=0x0f in psx_passcode_decode_to_mission_selector.
  2. At 0x8003ed10, this branch clears psx_level_runtime_header_state (DAT_80068ab0) to 0.
  3. Function return is not 0 on this branch.
  • Due to delay-slot flow (0x8003ecac, 0x8003ed04), v0 remains 0x10 on return.
  1. psx_passcode_screen_eval_current_entry stores raw decode return 0x10 into DAT_80068a8c and maps through DAT_80063e54[0x10].
  • From live table bytes, DAT_80063e54[0x10] = 0x3f.
  1. In caller flow (0x80034d84), s0 is this mapped value (0x3f), so beq s0,zero does not fire.

Exact apply/load behavior for NRTN / ?RTN lane

  1. Because s0 != 0, caller reaches jal 0x80021138 at 0x80034dcc.
  2. psx_passcode_apply_mission_selector_and_stage reads raw selector from DAT_80068a8c; for this path it is 0x10.
  3. Switch case for raw selector 0x10 is 0x8002142c (index 0x0f after selector-1).
  • writes DAT_800675e4 = 0x1d
  • sets mode byte DAT_80067379 = 2
  • calls common apply helper with a0 = 0x36
  1. This is consistent with the commonly described office-loading lane, but the exact user-facing mission label remains conservatively treated as "developer-office candidate" until runtime label capture in the same session.

JL-9 gate-arm compatibility answer

  • NRTN / ?RTN cannot itself satisfy gate-arm precondition psx_level_runtime_header_state == 3.
  • It explicitly forces that state to 0 in decoder (0x8003ed10).
  • Therefore it does not coexist with the required non-hidden gate-arm predicate at 0x800232c0..0x800232f0 (hidden==0 && runtime_header_state==3) unless another later path re-primes header state to 3.

Confidence:

  • high (~0.95) on decode return/write/apply control flow above (direct disassembly evidence)
  • medium-high (~0.78) on exact player-visible "developer office" naming for a0=0x36 without paired runtime UI/string capture

NRTN -> Office Event -> ?0SR/L0SR -> 0x1e hypothesis check (2026-04-11 live pass)

Target hypothesis tested against live SLUS_002.68 structure:

  1. enter NRTN to load dev office
  2. let an in-level office event arm psx_debug_extra_channel_gate
  3. enter hidden ?0SR/L0SR
  4. press input code 0x1e (R1 + Circle)

Verdict: structurally plausible but currently unproven as an exact deterministic player script.

Exact reasons from live code evidence:

  • psx_debug_extra_channel_gate remains one-writer/one-reader in this image:
    • write at 0x800232f0 in psx_set_debug_extra_channel_gate
    • read at 0x8002fff4 in psx_debug_grant_weapon_channels_and_ammo
  • gate-arm writer requires hidden_flag==0 and runtime_header_state==3 at the write site (0x800232c0..0x800232f0), so arming is structurally pre-hidden.
  • hidden path (?0SR/L0SR, special index 0x10) sets psx_hidden_passcode_flag at 0x8003ed28 and returns selector 0, with selector-0 branch skip around 0x80034d84.
  • grant trigger path requires hidden mode and input 0x1e (0x80013154..0x80013174), so trigger is structurally post-hidden.
  • in-level gate-arm routing is real and dispatch-based (0x800214ac..0x800215f8): it compares against DAT_80063e68[current_level], then indirect-calls DAT_800640a0[index]; slot 0x0f resolves to 0x800230e4 and uses byte args from the action record.

Why this is not yet fully proven:

  • static evidence still does not name one exact player-visible "office event" that deterministically emits the required writer tuple (slot 0x0f, param_2==0x0a, param_3==0x04) in dev office specifically.
  • special-passcode sibling behavior (?RTN lane clears runtime_header_state in decoder) means the exact NRTN practical role still needs one runtime trace confirmation in the same session, not another folklore inference.

Confidence split for this exact hypothesis:

  • high (0.89) that the two-phase shape (pre-hidden arm, post-hidden trigger) is structurally correct.
  • medium (0.67) that the concrete user recipe starting specifically from NRTN is correct as stated.
  • overall classification for the exact NRTN -> office event -> ?0SR/L0SR -> 0x1e claim: currently unproven, plausible.

Latest live MCP ownership pass on active SLUS_002.68 now tightens the storage model around your dump split (0x801456fc..0x80145748 static across dumps, 0x8014574c..0x801457d0 changed):

  • code-side ownership is channel-state based, not a direct contiguous owned-id array:
    • enable/query bit is byte psx_marker_channel_runtime_block[(channel*4)+0x34]
    • per-channel step/ammo state is byte psx_marker_channel_runtime_block[(channel*4)+0x6c]
    • active selected channel is byte psx_marker_channel_runtime_block+0x32
  • this lines up with the dump boundary: if runtime block base is near 0x80145718, then +0x34 is exactly 0x8014574c (start of the changed range), and +0x6c/+0x84/+0x8c also sit inside the changed range.
  • commit/resolver path remains table-driven (channel*10 rows), not contiguous-id-list driven:
    • psx_apply_channel_effect_and_commit_selected_item_id loads channel_commit_row_table[(channel*10)+9] at 0x8002f15c
    • that committed id then resolves weapon rows via idx*0x26 from 0x8006466a
  • debug bulk grant is now explicitly separated from single-channel unlock helper in the live database:
    • 0x8002fd90 renamed to psx_debug_grant_weapon_channels_and_ammo
    • function bulk-calls channel unlocks 0x04..0x0c and conditionally 0x0d behind psx_debug_extra_channel_gate read at 0x8002fff4

Current best read is stable on four points:

  • JL-9 is a real executable-backed weapon-definition row in the PSX build, not a stray string.
  • JL-9 is now also user-verified in emulator memory editing as a working selectable weapon with unique sprite/behavior, not just a table-resident leftover.
  • the extra hidden/debug-conditioned late weapon lane maps to JL-9, not JL-2.
  • step 2 of the old JL-9 recipe is now much clearer: it is not a second passcode-screen action, but an indirect in-level scripted handler dispatch that reaches psx_set_debug_extra_channel_gate through slot 0x0f with byte args 0x0a,0x04.
  • hidden ?0SR / L0SR also no longer looks like a level-loading code; the hidden branch returns selector 0 in the shared passcode-screen caller and skips the normal mission/apply-load path.
  • the checked binary/Crusader - No Remorse Memdump Weapons.bin artifact is a PSX VRAM dump and does not directly expose weapon-slot RAM.
  • the checked binary/Crusader - No Remorse Weapons Main Ram.bin artifact is plausible main RAM, but it still does not by itself distinguish the selected JL-? slot as JL-2 versus JL-9.
  • the current JL-9 debug-path read is now tighter in two separate ways: input code 0x1e is now closed to pad mask 0x2800 and high-confidence R1 + Circle, while the remaining recipe gap is the exact pre-hidden player action that reaches the param_2==0x0a,param_3==4 gate-arm writer path.

What step 2 actually means (2026-04-11 clarification)

The earlier wording for step 2 was too abstract. Current live code evidence says:

  1. Step 2 is not "enter something else on the passcode screen before the level loads."
  2. Step 2 is not the hidden passcode itself.
  3. Step 2 is an in-level scripted/control event that indirectly dispatches handler slot 0x0f, which is psx_set_debug_extra_channel_gate.
  4. The actual gate-arm write only happens when that handler is called with byte args 0x0a and 0x04.

So the practical model is now:

  • step 1: use a normal passcode path that leaves runtime_header_state == 3
  • level loads / control flow continues into gameplay
  • step 2: some scripted in-level event fires and reaches the slot-0x0f handler with args 0x0a,0x04, arming psx_debug_extra_channel_gate
  • step 3: use the hidden passcode path (?0SR / L0SR) to set hidden mode without taking the ordinary level-load branch
  • trigger: press R1 + Circle (0x1e) while hidden is active so debug grant runs and consumes the already-latched extra gate

The remaining unknown is therefore not "what button do I press for step 2?" but "what concrete in-level scripted event emits slot 0x0f with args 0x0a,0x04?"

Gate survivability across load/menu transitions (2026-04-11 live pass)

Scope of this pass was the exact user question: does psx_debug_extra_channel_gate (0x8006739d) survive level loads, passcode transitions, main-menu returns, and hidden-passcode activation in one running session.

Direct executable evidence:

  1. psx_debug_extra_channel_gate still has exactly one recovered writer and one recovered reader in this image.
  • writer: 0x800232f0 in psx_set_debug_extra_channel_gate (0x800230e4)
  • reader: 0x8002fff4 in psx_debug_grant_weapon_channels_and_ammo (0x8002fd90)
  • a direct neighborhood write scan over 0x80067380..0x800673b0 found many writes to nearby state bytes (0x80067384, 0x8006738c, 0x8006739c, 0x800673a0, 0x800673a4), but no additional write to 0x8006739d.
  1. Level-load/session loop logic does not recover a static clear of 0x8006739d.
  • psx_level_session_loop (0x8002b8ec) loads WDL bundles and resets several per-session flags, and it does clear psx_hidden_passcode_flag on timer expiry at 0x8002b9e4.
  • no direct store to 0x8006739d appears in this loop.
  1. Main-menu/UI reset helpers seen in this lane do not recover a static clear of 0x8006739d.
  • FUN_800350e4 and FUN_800352d4 reset menu/passcode state (DAT_80067384, selector bytes, control flags, runtime-header staging), but no write to 0x8006739d is recovered.
  1. Hidden passcode activation and normal passcode application diverge exactly where expected.
  • decoder special index 0x10 sets psx_hidden_passcode_flag=1 in psx_passcode_decode_to_mission_selector at 0x8003ed28 and returns selector 0.
  • in the passcode caller block around 0x80034d84, selector 0 skips the apply/load branches (psx_passcode_apply_selector_to_mode_0x0d, psx_passcode_apply_selector_to_mode_0x15, psx_passcode_apply_mission_selector_and_stage) because of the explicit beq s0,zero,... branch.
  • practical consequence: hidden-code entry does not itself force the same normal apply/load branch used by nonzero mission selectors.
  1. Gate-arm and grant-entry still require opposite hidden-flag states.
  • gate arm at 0x800232f0 requires psx_hidden_passcode_flag==0 and psx_level_runtime_header_state==3.
  • debug grant entry at 0x80013154..0x80013174 requires psx_hidden_passcode_flag!=0 and input code 0x1e.

Answer to the two-phase feasibility question:

  • In one running session, the two-phase recipe remains feasible.
  • Strongest current ordering is still: arm gate first in non-hidden state, then activate hidden mode and trigger input code 0x1e.
  • Based on current static evidence, step 2 (hidden activation + trigger) is after the gate-arm event and can happen after a level load in the same process/session; no static clear of 0x8006739d was recovered across the inspected load/menu transition helpers.
  • Step 2 before gate-arm is not consistent with the recovered predicates, because gate-arm explicitly requires hidden flag off.

Confidence:

  • high (~0.92) on one-writer/one-reader gate model in this image
  • high (~0.88) on hidden-vs-nonhidden polarity constraint forcing a two-phase order
  • medium-high (~0.76) on practical persistence through all menu/load routes, because static evidence found no clear but an unresolved pointer-indirect/runtime write path is still theoretically possible

Passcode screen semantics closure: what step 1 and step 3 mean (2026-04-11 live pass)

This closure pass targeted user-facing behavior, not just branch predicates: are normal and hidden passcodes entered on the same screen, do they immediately transition out, and is the current numbered sequence operationally safe.

Direct code-backed findings:

  1. Normal and hidden passcodes are decoded through the same passcode-screen evaluator.
  • psx_passcode_screen_eval_current_entry (0x80034e38) builds the same 4-byte candidate from current screen-entry bytes and always calls psx_passcode_decode_to_mission_selector.
  • Hidden special decode (index 0x10, ?0SR/L0SR) is set in that shared decoder at 0x8003ed28 (psx_hidden_passcode_flag = 1), not in a separate hidden-only input UI.
  1. The immediate passcode-screen control flow is value-sensitive in the unnamed caller block around 0x80034c14.
  • On the first eval site (0x80034c14), return 0 follows the reset/early-return branch (0x80034c24..0x80034cdc) rather than the acceptance/transition path.
  • Nonzero eval values proceed through 0x80034d00 (store selected result) and then into transition/setup calls (0x800380d8, 0x8003a46c) before additional per-level setup (0x80034d2c onward).
  1. User-facing meaning of the sequence steps is now tighter:
  • Step 1 (normal passcode with delta=>state 3) means: in the same passcode-entry screen, enter a normal code that reaches the non-special first-char delta lane and writes psx_level_runtime_header_state = 3 at 0x8003ed58.
  • Step 3 (hidden passcode) also means: use that same passcode-entry screen/evaluator path to hit special decode 0x10 (0x8003ed28), not a separate hidden-code menu.
  1. Operational risk in the old sequence wording:
  • Because hidden decode and normal decode share this screen and the caller path has distinct zero/nonzero branches, a literal read of "do in-level step 2, then return later for step 3" is not yet proven as one deterministic minimal player script.
  • Current conservative status is therefore: branch predicates are closed, but exact user-visible ordering remains partially unresolved until the param_2==0x0a,param_3==4 producer is observed in one live trace.

Conservative verdict on sequence correctness:

  • The old sequence is directionally correct about the two-phase logic (pre-hidden gate arm + hidden input trigger),
  • but it is operationally under-specified and should not be treated as a guaranteed one-try recipe without the remaining caller-context closure.

Confidence:

  • high (~0.90) that hidden and normal passcodes are entered through the same screen/evaluator path
  • high (~0.85) that the caller block has immediate branch-divergent behavior for return 0 vs nonzero
  • medium (~0.64) on exact practical player-order script, pending direct producer closure for the 0x0a/4 gate-arm event

Gate-arm caller path closure around 0x800230e4 (2026-04-11 live pass)

Scope of this pass was the exact writer condition at 0x800232f0 and the concrete upstream state flow that can make it true before hidden+input grant.

Direct code facts now pinned:

  1. The gate write at 0x800232f0 is in psx_set_debug_extra_channel_gate (0x800230e4) and is only reachable in the local jump-table branch for param_2==0x0a and param_3==4.
  • jump table at 0x80010550 maps entry 4 to block 0x800232a0
  • that block performs FUN_8002ba90() and FUN_8002ba78() then evaluates hidden/state guards and conditionally stores 1 to DAT_8006739d
  1. The exact store predicate is now explicit and comment-anchored in-session:
  • hidden must still be off: DAT_80067454 == 0 (0x800232c0, 0x800232d0 branch)
  • runtime header must be 3: DAT_80078ab0 == 3 (0x800232dc, 0x800232e4 branch)
  • only then store: sb 1, DAT_8006739d at 0x800232f0
  1. runtime_header_state==3 comes from normal (non-special) passcode decode math, not the hidden/special branches.
  • psx_passcode_decode_to_mission_selector (0x8003ec8c) normal lane writes:
    • DAT_80078ab0 = code[0] - (DAT_80064bbc[idx] + 0x1b) at 0x8003ed58
    • accepted only when result <=3
  • special branches do not produce the required prime state:
    • index 0x10 (?0SR / hidden) sets DAT_80067454=1
    • index 0x0f (?RTN) forces DAT_80078ab0=0
  1. The strongest player-facing prime model is now: valid non-hidden mission/passcode entry in the normal decode lane with first-char delta resolving to 3.
  • this yields the required precondition DAT_80078ab0==3 while hidden is still 0
  • once the param_2==0x0a,param_3==4 event path runs, DAT_8006739d can latch to 1
  1. Hidden+input grant remains a later, separate phase with opposite hidden flag polarity.
  • grant entry in psx_object_update_runtime_input_modes requires DAT_80067454!=0 and input code 0x1e at 0x80013154..0x80013174
  • grant helper reads DAT_8006739d at 0x8002fff4 and only then executes extra unlock(0x0d) at 0x80030004

Additional caller-path closure from later passes:

  1. The gate writer is reached through an indirect level-gated dispatcher, not a direct passcode callback.
  • dispatcher block at 0x800214ac..0x800215f8
  • 0x800215bc compares opcode index against DAT_80063e68[current_level]
  • 0x800215dc calls DAT_800640a0[index] via jalr
  • slot 0x0f in DAT_800640a0 resolves to 0x800230e4 (psx_set_debug_extra_channel_gate)
  • handler byte args are loaded from action-record pointers at 0x800215cc and 0x800215e0
  • current best narrowed trigger tuple is therefore (dispatch slot 0x0f, param_2 0x0a, param_3 0x04) in a small level-scripted family, not a manual UI/menu callback

Practical sequence now supported by executable evidence:

  1. Enter a normal valid passcode whose first-char delta lane sets runtime_header_state to 3 (hidden remains off).
  2. Let gameplay continue until the in-level scripted/control event fires that reaches slot 0x0f / psx_set_debug_extra_channel_gate with param_2==0x0a,param_3==4, so DAT_8006739d is armed.
  3. Enter hidden passcode (?0SR / canonical L0SR when selector is 0); current passcode-screen flow evidence says this shared decoder branch sets hidden flag but skips the normal nonzero mission/apply-load branch.
  4. Perform input code 0x1e (project mapping currently R1+Circle) to run debug grant; extra 0x0d unlock executes because gate is already set.

Confidence:

  • high (~0.91) on gate write predicate and branch identity (0x800232a0 path)
  • high (~0.88) that runtime_header_state==3 prime comes from normal decode lane, not special hidden/sibling specials
  • medium (~0.62) on one exact user-visible action name for step 2 (param_2==0x0a,param_3==4 producer), because this pass closed the argument/value lane and surrounding passcode state flow but did not yet recover a single named UI handler with clean function boundaries in the 0x80022e8..0x800230e0 block

Executable-only JL-9 validation lane (2026-04-11)

Scope of this pass was strict: recover an exact user-facing JL-9 enable sequence from executable evidence only across passcode decode, input chord decode, gate write, grant call, and selected-id commit path.

Recovered chain (all executable-backed):

  1. Hidden passcode decode arm:
  • psx_passcode_decode_to_mission_selector (0x8003ec8c) sets psx_hidden_passcode_flag at 0x8003ed28 on special decode index 0x10.
  • index 0x10 bypasses first-character validation and uses only the entry[1..3] transformed triplet check (-0x1b) against 0x80064bd0/0x80064be4/0x80064bf8.
  1. Input chord decode to grant trigger:
  • psx_object_update_runtime_input_modes (0x80012c30) gates the grant path on psx_hidden_passcode_flag!=0 and decoded input code 0x1e (0x80013154..0x80013174).
  • psx_input_map_install_profile (0x80042ec4) maps code 0x1e to pad mask 0x2800 in all recovered profile branches, so the practical chord remains R1 + Circle under the project pad-bit model.
  1. Extra JL-9 gate write:
  • psx_set_debug_extra_channel_gate (0x800230e4) writes psx_debug_extra_channel_gate=1 at 0x800232f0 only when both conditions hold:
    • psx_hidden_passcode_flag == 0 (0x800232c0 / 0x800232d0 branch)
    • psx_level_runtime_header_state == 3 (0x800232dc..0x800232e4)
  1. Grant call and final extra unlock:
  • psx_object_update_runtime_input_modes calls psx_debug_grant_weapon_channels_and_ammo at 0x80013174.
  • grant helper reads gate at 0x8002fff4; if nonzero, branch at 0x80030004 unlocks channel 0x0d (JL-9 lane).
  1. Selected-id commit sink used by watched selected-byte lane:
  • psx_apply_channel_effect_and_commit_selected_item_id loads committed row id from channel_commit_row_selected_item_id[(channel*10)+9] at 0x8002f15c and stores to nested runtime +0x1c at 0x8002f168, also mirroring to committed_selected_item_id at 0x8002f170.
  • this is the executable-backed row-id commit path that underpins the observed selected-byte 00..0d behavior (0x0d for JL-9).

Exact-sequence blocker from executable evidence only:

  • one link remains unclosed for a fully deterministic player-facing recipe: the concrete user-visible action/context that drives the param_2=='\\n' / case 4 path in psx_set_debug_extra_channel_gate (0x800230e4) while hidden flag is still 0.
  • without that mapping, code proves a two-phase ordering requirement but not one exact minimal button/menu script that always arms the gate before hidden grant input.

Minimal next probe to close the blocker:

  • in one live trace, breakpoint/log 0x800232f0 and caller args into 0x800230e4 (especially param_2 / param_3) while performing candidate player actions around passcode entry and mode transitions.
  • first observed user-facing action that reaches this writer under hidden=0 && runtime_header_state=3 closes the final exact-step gap.

Focused live follow-up on 0x80067944 and the surrounding 0x80067938..0x80067958 block now narrows one unresolved identity question:

  • 0x80067944 has no recovered static xrefs in this image under word, halfword, or byte probe passes (get_data_uses + operand scans), so it is currently not supported as a selected local weapon id or selected row id owner.
  • 0x80067938 is reaffirmed as psx_ctor_placement_section_ptr, installed in wdl_resource_bundle_load_by_index and consumed in psx_apply_deferred_control_command as constructor/deferred-control section state, not as weapon selection state.
  • nearby block ownership remains mixed: 0x8006793c/0x80067940/0x80067948 are consumed by psx_object_update_runtime_input_modes for input-mode dispatch helpers, while 0x80067954/0x80067958 are draw/disp environment flip/progress state used by present/spec-upload helpers.
  • one unnamed helper was promoted conservatively from direct behavior evidence: FUN_800461d0 -> psx_draw_progress_overlay_and_swap_drawenv.

The newest user-provided emulator verification replaces the earlier JL-?=11 shorthand with a stronger selected-weapon mapping, while the starter-only compare still retracts one earlier storage claim:

  • verified selected-weapon byte at 0x8014577e maps directly as row-id domain:
    • 00 no weapon / invalid gun
    • 01 RP-16
    • 02 RP-22
    • 03 RP-32
    • 04 SG-A1
    • 05 AC-88
    • 06 PA-31
    • 07 EM-4
    • 08 PL-1
    • 09 UV-9
    • 0A GL-303
    • 0B AR-7
    • 0C JL-2
    • 0D JL-9
  • the new starter-only RAM compare shows 0x1456fc..0x145748 is static across dumps and therefore is not the owned-weapon inventory list
  • the dynamic region is instead 0x14574c..0x1457d0, with the strongest current field closure at 0x14577e:
    • all-weapons dump byte at 0x14577e: 0c
    • starter-only dump byte at 0x14577e: 02
    • current best read: selected weapon row-id byte inside a nested runtime state block, not a contiguous owned-id slot array
  • the separate watched field at file offset 0x67944 still changes (0x0000000b vs 0x00000001), but live executable passes did not recover direct static xrefs for 0x80067944, so it remains an unproven watch field rather than a safe patch target

New correction from live MCP reconciliation with the user-verified selected-weapon byte mapping:

  • treat the verified byte mapping at 0x8014577e as the selected-weapon row-id domain (00..0d: none/invalid through JL-9).
  • keep the argument domain at 0x8002ef34 separate: callers pass a compact channel/local code, then 0x8002f15c converts through channel_commit_row_selected_item_id[(channel*10)+9] into the committed row id that is written to the nested runtime field at +0x1c (0x8002f168).
  • practical correction: prior shorthand that could read as "local id equals committed row id" is too loose; the robust model is now caller channel/local code -> commit table -> committed row id (00..0d), and the user-verified byte at 0x8014577e belongs to that committed row-id domain.
  • static xrefs cannot directly prove absolute runtime RAM addresses such as 0x8014577e, so address closure remains runtime-evidence-backed plus commit-path-backed rather than static-xref-backed.

The strongest remaining unknown is no longer whether JL-9 exists. It is now split into two narrower questions:

  • what exact runtime conditions make the late JL-9 unlock visible in normal play timing,
  • and what JL-2 actually is as a normal ammo-using weapon, since JL-2 AMMO is present while no matching plain JL-9 AMMO string has been recovered.

Gate and hidden-flag lifecycle closure (2026-04-11 live pass)

Scope of this pass was the exact lifecycle of psx_debug_extra_channel_gate (0x8006739d) and psx_hidden_passcode_flag (0x80067454) across session init, passcode entry, and debug grant.

Recovered reference set is now tight:

  • psx_debug_extra_channel_gate (0x8006739d):
    • writer: 0x800232f0 in psx_set_debug_extra_channel_gate
    • reader: 0x8002fff4 in psx_debug_grant_weapon_channels_and_ammo
    • recovered static clears/resets: none
  • psx_hidden_passcode_flag (0x80067454):
    • sets: 0x8003ed28 (psx_passcode_decode_to_mission_selector, special index 0x10), 0x8002bab8 (psx_hidden_passcode_arm_runtime_state)
    • clear/reset: 0x8002b9e4 (psx_level_session_loop, timer-expiry branch)
    • readers: 0x80013154 (psx_object_update_runtime_input_modes), 0x800232c0 (psx_set_debug_extra_channel_gate)

State-machine facts now closed:

  1. Extra unlock gate arm condition is strict and opposite to the grant-entry hidden condition.
  • At 0x800232c0..0x800232f0, psx_set_debug_extra_channel_gate writes psx_debug_extra_channel_gate=1 only when:
    • psx_hidden_passcode_flag == 0
    • psx_level_runtime_header_state == 3
  1. Debug grant entry requires hidden mode to be active.
  • At 0x80013154..0x80013174, psx_object_update_runtime_input_modes returns early unless psx_hidden_passcode_flag != 0.
  • If active, decoded input code 0x1e calls psx_debug_grant_weapon_channels_and_ammo.
  1. Extra JL-9 unlock check is a separate late latch.
  • At 0x8002fff4, grant helper reads psx_debug_extra_channel_gate.
  • If nonzero, branch at 0x80030004 unlocks channel 0x0d (JL-9 lane).
  1. Hidden flag has explicit timed clear behavior; extra gate currently does not.
  • psx_hidden_passcode_arm_runtime_state seeds DAT_800673cc=2000 and sets hidden flag at 0x8002bab8.
  • psx_level_session_loop clears hidden flag at 0x8002b9e4 when timer reaches zero.
  • No recovered static clear path writes 0 to psx_debug_extra_channel_gate in this image.

Practical persistence answer for the requested A -> B -> R1+Circle model:

  • Strongest executable-backed result is yes: the gate can persist long enough for a two-phase flow because we recovered one set and no static clear for psx_debug_extra_channel_gate, while hidden mode can be armed later and used within its own timer window.
  • This supports sequence shape:
    • step A: satisfy the non-hidden gate-arm condition (hidden=0, header-state 3) so psx_debug_extra_channel_gate becomes 1
    • step B: enter hidden passcode (0x10 decode branch, canonical L0SR form when selector is 0) to arm hidden mode
    • trigger: press input code 0x1e (practical mapping in this project remains R1 + Circle) before hidden timer expiry
    • effect: debug grant path runs and late extra branch includes unlock(0x0d)

Confidence:

  • high (~0.93) on writer/reader/clear sets listed above (direct xref and instruction evidence)
  • high (~0.90) on two-phase ordering requirement (opposite hidden-flag conditions between gate arm and grant entry)
  • medium-high (~0.78) on persistence duration beyond hidden timer window, because no static clear is recovered for the extra gate but pointer-indirect or un-recovered dynamic writes are still theoretically possible

RP-16 status closure (2026-04-11 pass)

Scope of this pass was narrowed to user-observed selected-weapon id 0x01 and whether it should be treated as a real usable RP-16 lane, an invalid slot, an earlier variant, or a startup placeholder.

Direct executable findings:

  1. Weapon-definition row is real and populated.
  • row 0x01 at 0x80064690 decodes as RP-16 and carries nonzero row fields (+0x1c=0x01, +0x20=0x03e8, +0x24=0x06), unlike pure null/blank filler.
  1. Primary shop acquire lane does not include row 0x01.
  • psx_weapon_shop_try_apply_entry front path (param_1 < 10) uses direct unlock helper psx_weapon_channel_unlock_and_seed_markers and shop table bytes 03 04 05 06 07 08 09 0a 0b 0c.
  • practical consequence: direct shop unlock progression reaches JL-2 (0x0c) but excludes RP-16 (0x01) and RP-22 (0x02).
  1. 0x01 appears in shop lookup, but in secondary ammo branch, not direct unlock branch.
  • shop table slot 10 byte is 0x01 (0x80064b9a), but this path enters the 0x0a..0x0e branch that calls 0x8002e32c (ammo top-up helper), not unlock helper 0x8002e5f0.
  • this supports "defined id in economy tables" but not "normal explicit acquisition of RP-16 weapon row" from this lane.
  1. Hidden/debug lane remains focused on late ids, not 0x01.
  • psx_debug_grant_weapon_channels_and_ammo still closes around normal progression plus extra 0x0d gate behavior; no new fixed immediate 0x01-specific unlock site was recovered in this pass.

HUD/name path closure for RP-16 in this pass:

  • full weapon-name identity remains row-driven: row 0x01 inline bytes at 0x80064690+2 decode to RP-16.
  • HUD short-label rendering uses selected-id-indexed lookup bytes in FUN_800455d4 from tables around 0x80064e90 and 0x80064e9c (selected id minus one).
  • this supports RP-16 as a represented display id, even where normal-lane unlock evidence is weaker than row existence.

Current classification for RP-16 (best supported):

  • not invalid/empty: row is concrete and populated.
  • not currently proven as normal direct unlock lane: primary shop/loadout unlock path evidence emphasizes >=0x03 progression and caps at 0x0c in the direct shop lane.
  • strongest fit right now: real defined early weapon row that behaves like a legacy/startup/placeholder-capable entry in this image, with table presence and UI representation but no newly recovered dedicated normal acquisition proof in this pass.

Confidence:

  • high (~0.89) that row 0x01 is real structured weapon data (RP-16), not random padding.
  • high (~0.84) that direct shop unlock progression excludes 0x01 in the primary acquisition branch.
  • medium (~0.61) on final gameplay role label (legacy/startup/placeholder) until one concrete non-debug in-mission acquisition or initialization writer is recovered.

RP-16 startup/default-init closure (2026-04-11 pass)

Focused live MCP pass on active SLUS_002.68 to answer only this question: does RP-16 (row/id 0x01) get seeded as startup/default weapon via fresh-game init, difficulty-driven starts, mode transitions, or mission/loadout init?

Direct startup/init findings:

  1. Post-load reset explicitly clears selected-id state before init dispatch.
  • psx_level_post_load_runtime_reset writes committed_selected_item_id = 0 at 0x80039f68.
  • from the same function, startup path then dispatches mode actions (8, optionally 2, then 4), not a fixed 0x01 commit.
  1. Mission/loadout init does not perform a fixed RP-16 selected-id write.
  • psx_weapon_channels_init_mode_loadout (0x8002f814) is mode-table driven from psx_level_channel_table_80063e68 and applies unlock/ammo helpers via fallthrough; no fixed immediate 0x01 selected-id commit is present.
  • psx_weapon_channels_apply_mode_transition_state (0x8002f278) sets active channel state to 2 or 3 in the observed startup branch (0x8002f468 / 0x8002f49c), not channel/id 0x01.
  1. The selected-id global has only two recovered writers in this image.
  • writer A: reset-to-zero at 0x80039f68.
  • writer B: table-based commit sink in psx_apply_channel_effect_and_commit_selected_item_id at 0x8002f170.
  • no dedicated startup writer with a fixed immediate 0x01 was recovered.
  1. Fixed-immediate commit callsites found in this pass do not support RP-16-as-default.
  • recovered immediate dispatches use a0=0x11, a0=0x12, and one context-specific a0=0x01 action lane in unnamed gameplay/control handlers (0x8001ede8, 0x8001ef08, 0x8001f068, 0x80021930, 0x80022624), not the named startup/loadout reset path.
  • this keeps low-id usage structurally possible outside startup, but does not convert RP-16 into a proven default-start weapon.
  1. Default/loadout table context remains non-startup evidence.
  • shop/channel map bytes at 0x80064b90 still include 0x01 (... 0c 01 05 04 ...), but this remains economy/action-path evidence rather than a startup seed proof.

Current startup/default verdict for RP-16:

  • not proven startup/default weapon in fresh-game init, difficulty/mode-transition apply, or mission/loadout init paths recovered in this pass.

JL-9 producer-side authored-source closure pass (2026-04-11 live MCP)

Scope of this pass was limited to the producer side of the action-record pointer frame consumed by psx_level_gate_slot_dispatch_from_action_record (0x800214ac), with priority on finding one concrete upstream authored context for tuple (slot 0x0f, arg1 0x0a, arg2 0x04).

Strongest producer-side clue recovered

The strongest new clue is now loader/constructor explicit:

  1. psx_load_type_state_banks (0x800391f0) installs psx_type_simple_component_bank[type] from the level bundle type-state blob (SPEC_A.WDL/L*.WDL path via wdl_resource_bundle_load_by_index at 0x8003977c).
  2. psx_object_create_simple_record seeds component program_base and pc from that exact bank at 0x80024c60 and 0x80024c88.
  3. psx_run_object_behavior_program_tick then executes words from that component stream and calls psx_object_behavior_opcode_dispatch at 0x80026740.
  4. psx_object_behavior_opcode_dispatch routes opcode 54 through psx_behavior_opcode_handler_table (0x800641ac) to psx_behavior_subopcode_dispatch (0x80027ecc), which then routes subop 49 through psx_behavior_subop_handler_table (0x80063610) to sink 0x800214ac.

Practical implication: the pointer-frame lane feeding slot/arg bytes into 0x800214ac is now best modeled as authored type-state behavior program content loaded per level, not sink-local constants.

Tuple classification update

Current best classification for (0x0f,0x0a,0x04) is now:

  1. authored-static at source context (type-state behavior payload in level bundle),
  2. then runtime-index resolved in dispatcher frame construction when mask bits request base + index*4 slot-pointer mapping.

So this tuple is not strongest as a pure runtime remap invention; runtime remap appears to be the transport mechanism over authored behavior-program operands.

Exact live Ghidra changes in this pass

Decompiler comments added:

  • 0x80039250: producer provenance note on psx_type_simple_component_bank[type] install from level/LSET type-state blob.
  • 0x80024c60: constructor note that component program base/pc come from psx_type_simple_component_bank[type] (upstream authored source lane).
  • 0x80026740: behavior-tick note that opcode/mask/args are read from component pc stream and feed psx_object_behavior_opcode_dispatch.

No function renames were applied in this pass.

Remaining open item (narrowed)

Still open is one concrete type row + map context instance whose loaded behavior stream emits the exact 54 -> 49 producer record that resolves to (slot 0x0f, arg1 0x0a, arg2 0x04) at runtime. The unresolved part is now specific row attribution, not producer subsystem identity.

  • still a real executable-backed row with non-startup lane presence.
  • best current label: placeholder/legacy-capable early row with unresolved normal acquisition role, not a demonstrated startup default.

Confidence (startup/default question only):

  • high (~0.86) that current named startup/loadout/mode-transition initializers do not hard-seed selected id 0x01.
  • medium (~0.63) that no indirect startup-side table commit resolves to 0x01 in unseen/unnamed init stubs, because several nearby callsites still live in undefined function ranges.

RP-16 startup/default recheck (2026-04-11 live MCP follow-up)

This follow-up pass pushed specifically on undefined nearby init stubs, startup selected-id writes, active-channel writes, and difficulty/mode tables to test any indirect RP-16 (0x01) seed route.

Direct findings:

  1. committed_selected_item_id still has exactly two recovered writes in this image.
  • reset write: 0x80039f68 in psx_level_post_load_runtime_reset (=0)
  • commit sink write: 0x8002f170 in psx_apply_channel_effect_and_commit_selected_item_id
  • no third startup/default writer was recovered.
  1. Startup mode-action dispatch remains bounded and non-committing.
  • startup callsites dispatch mode_action=8 then 2/4 (0x80039fa4, 0x8003a014, 0x8003a01c; additional observed stub callsite at 0x8003e6a0 dispatching 8).
  • these actions drive loadout/transition/seed tables but do not directly call the selected-id commit sink.
  1. Active-channel writes in startup lanes do not imply RP-16 commit.
  • psx_weapon_channels_apply_mode_transition_state sets psx_marker_channel_runtime_block+0x32 to 2 or 3 in the startup branch.
  • loadout unlock/ammo helpers only initialize when +0x32==0, then write the channel argument used by that helper path; no startup helper immediate observed here seeds channel 0x01 as selected-id commit.
  1. Difficulty/mode table path still does not resolve to selected-id 0x01.
  • psx_level_channel_table_80063e68 feeds DAT_80078a8c in startup (0x8002f2a0, 0x8002f868, 0x8002f978, 0x80039fe8).
  • table-driven startup channels resolve into commit-row selected bytes at channel_commit_row_selected_item_id (0x80064355, +9 in each 10-byte row), and scanned rows 0x00..0x19 contain no sel=0x01 byte.
  1. Undefined nearby commit callsites found in this pass do not establish startup-default RP-16.
  • recovered no-function callsites into commit helper include 0x8001ede8 and 0x8001ef08 (a0=0x11), plus additional gameplay/control lanes from prior passes.
  • these are outside the named startup reset/loadout transition chain.

Conservative live Ghidra artifact updates from this pass:

  • rename: 0x8002fd80 -> psx_marker_channel_set_mode6_only
  • decompiler comments:
  • 0x80039f68 (startup reset selected-id clear)
  • 0x8002f170 (commit sink table-write semantics)
  • 0x8002f2a0 (mode-table source and non-0x01 implication)
  • disassembly comments:
  • 0x80064355 (selected-id table semantics and no 0x01 in scanned rows)
  • 0x8003e6a0 (observed startup/transition stub dispatching mode action 8)

Updated verdict after this follow-up:

  • startup/default RP-16 remains ruled out in recovered startup/init code paths for active SLUS_002.68.
  • RP-16 (0x01) remains a real row and remains reachable in non-startup contexts, but this pass found no evidence of startup/difficulty/mode-init seeding to selected-id 0x01.

Legit Acquisition Closure (2026-04-11 pass)

Scope of this pass was narrowed to legitimate JL-9 acquisition paths versus hidden/debug leftovers, with emulator verification treated as ground truth for selected weapon id domain (0x8014577e, 0x0c=JL-2, 0x0d=JL-9).

Direct executable findings by lane:

  1. Normal loadout lane (psx_weapon_channels_init_mode_loadout):
  • fallthrough switch seeds baseline unlock/ammo progression and reaches ordinary channels, but does not perform a fixed immediate unlock of channel 0x0d
  • this lane supports normal progression to JL-2 (0x0c) and earlier channels, not a direct hardcoded JL-9 grant
  1. Shop lane (psx_weapon_shop_try_apply_entry):
  • unlock front-path is gated by param_1 < 10
  • DAT_80064b90[0..9] is 03 04 05 06 07 08 09 0a 0b 0c
  • practical result: direct shop unlock path is capped at 0x0c (JL-2) and does not directly issue 0x0d (JL-9)
  1. Scripted packed-action/pickup lane (psx_section0_dispatch_root_apply_packed_channel_actions):
  • action type 3 dispatches psx_weapon_channel_unlock_and_seed_markers(channel_byte) from decoded triplet data
  • triplet table is seeded at runtime by psx_section0_dispatch_root_seed_marker_channel_table from section0 marker records
  • this means scripted non-debug 0x0d is structurally possible only if authored level marker data actually supplies channel 0x0d; this pass did not recover a shipped-map proof row that does so in normal play
  1. Hidden/debug lane (psx_object_update_runtime_input_modes -> psx_debug_grant_weapon_channels_and_ammo):
  • psx_hidden_passcode_flag gate at 0x80013154 must be active before input code 0x1e can call debug grant
  • debug grant always unlocks through 0x0c, and conditionally unlocks 0x0d only when psx_debug_extra_channel_gate is nonzero
  • this remains the only recovered fixed-immediate unlock(0x0d) call site (0x80030004) in the current executable

Current verdict from executable evidence:

  • strongest supported path to JL-9 remains hidden/debug-conditioned
  • strongest supported normal gameplay acquisition lanes close at <= 0x0c (JL-2)
  • non-debug scripted 0x0d remains the only plausible legitimate exception, but is still unproven without a concrete shipped section0 marker/action row

Confidence:

  • high (~0.87) that fixed-code normal/shop paths do not directly grant JL-9
  • high (~0.90) that hidden/debug path can grant JL-9 via the 0x0d conditional unlock
  • medium (~0.52) on whether shipped non-debug section0 data ever drives a legitimate scripted 0x0d unlock

Executable-side condition closure (2026-04-11 pass)

Focused live MCP pass on active SLUS_002.68 to close the exact gate chain around your verified selected-weapon byte watch (0x8014577e) and the JL-9 late unlock lane.

What this pass closes directly:

  1. psx_debug_extra_channel_gate (0x8006739d) is still a one-writer/one-reader gate in this image:
  • writer: psx_set_debug_extra_channel_gate (0x800232f0)
  • reader: psx_debug_grant_weapon_channels_and_ammo (0x8002fd90) at 0x8002fff4
  1. psx_hidden_passcode_flag (0x80067454) writer/reader set is now explicit:
  • writers: psx_passcode_decode_to_mission_selector (0x8003ed28), psx_hidden_passcode_arm_runtime_state (0x8002ba9c), and clear/reset points in psx_level_session_loop (0x8002b9e4)
  • readers: psx_set_debug_extra_channel_gate (0x800232c0) and psx_object_update_runtime_input_modes (0x80013154)
  1. Unlock-capable call families for channel 0x0d are now bounded:
  • debug bulk grant: psx_debug_grant_weapon_channels_and_ammo (always includes 0x0c, includes 0x0d only when psx_debug_extra_channel_gate != 0)
  • scripted packed actions: psx_section0_dispatch_root_apply_packed_channel_actions can call psx_weapon_channel_unlock_and_seed_markers(channel) for data-driven channel bytes
  • normal loadout/shop paths call the same unlock helper but are mode/slot constrained; shop front path (param_1 < 10) maps channels 0x03..0x0c and does not directly include 0x0d
  1. Commit/read chain for visible selected weapon remains stable:
  • commit: psx_apply_channel_effect_and_commit_selected_item_id -> channel_commit_row_selected_item_id[(channel*10)+9]
  • sinks: nested player runtime field (... + 0x1c) and committed_selected_item_id (0x80078a90)
  • consumers include spawn/HUD-adjacent lanes (FUN_80014d04, FUN_80014eac, psx_spawn_contact_burst_simple_records)

0x8014577e closure status in this pass:

  • direct static xrefs from get_data_uses(0x8014577e) are still empty (expected for heap/runtime fields)
  • executable-side chain strongly supports that this watch sits in the same nested player runtime object family as the committed selected-id lane, but this pass did not recover one direct static instruction with absolute 0x8014577e

Practical classification after this pass:

  • strongest debug-only lane: hidden-passcode-gated input path that reaches psx_debug_grant_weapon_channels_and_ammo, then conditionally unlocks 0x0d through psx_debug_extra_channel_gate
  • plausible shipped-reachable lane: section0/script packed-action dispatcher can grant channels by data (including potential 0x0d if authored data uses it), but no new map/script row proving shipped non-debug 0x0d authoring was recovered in this pass
  • strongest normal-lane evidence remains <= 0x0c via loadout/shop constraints and earlier commit-table/model work

Final JL-9 enable sequence closure (2026-04-11 pass)

Focused live MCP pass on active SLUS_002.68 against the exact target points:

  • psx_hidden_passcode_arm_runtime_state (0x8002ba9c)
  • psx_set_debug_extra_channel_gate write site (0x800232f0, inside function entry 0x800230e4)
  • psx_debug_grant_weapon_channels_and_ammo (0x8002fd90)
  • extra gate read / extra unlock branch (0x8002fff4 / 0x80030004)

Direct gate facts now closed

  1. Final extra JL-9 unlock is definitely gated by psx_debug_extra_channel_gate:
  • disassembly at 0x8002fff4 reads lbu v0,0x9d(gp) (DAT_8006739d)
  • if nonzero, branch executes jal 0x8002e5f0 with a0=0x0d at 0x80030004
  • this is the only extra post-0x0c unlock site in this helper and maps to channel/id 0x0d (JL-9 lane)
  1. The extra gate is written only under a strict precondition:
  • writer instruction at 0x800232f0 stores 1 to DAT_8006739d
  • immediate guards in the same block require:
    • psx_hidden_passcode_flag == 0 (lw v1,0x7454(v1) then bne v1,zero,...)
    • psx_level_runtime_header_state == 3 (lw v1,-0x7550(v1) then bne v1,3,...)
  • practical write condition is therefore:
    • set extra gate only when hidden-passcode flag is currently off and runtime-header state is 3
  1. Calling the grant helper requires the opposite hidden-flag condition:
  • in psx_object_update_runtime_input_modes (0x80013154), debug path returns early unless psx_hidden_passcode_flag != 0
  • only then can decoded input code 0x1e reach psx_debug_grant_weapon_channels_and_ammo (call at 0x80013174`)
  1. Hidden-passcode flag lifecycle remains bounded and explicit:
  • set in psx_passcode_decode_to_mission_selector (0x8003ed28, special index 0x10 path)
  • armed/set in psx_hidden_passcode_arm_runtime_state (0x8002ba9c) with timer seed DAT_800673cc=2000
  • cleared/reset in psx_level_session_loop (0x8002b9e4) when timed armed state expires

Exact precondition model for JL-9 extra unlock

To execute the extra 0x0d unlock inside psx_debug_grant_weapon_channels_and_ammo, executable evidence now requires both:

  • psx_debug_extra_channel_gate != 0 at 0x8002fff4 (gate read)
  • hidden debug input path active (psx_hidden_passcode_flag != 0 and input code 0x1e) to enter the helper

And the gate itself is only set in a prior state where:

  • psx_hidden_passcode_flag == 0
  • psx_level_runtime_header_state == 3

One hidden action or two?

Current executable-side verdict is now:

  • strongest model is a two-phase hidden flow for guaranteed JL-9 extra unlock, because gate-write and grant-entry require opposite hidden-flag states (==0 vs !=0)
  • a single hidden action could only be sufficient if one action simultaneously establishes both states across time (for example gate already latched from an earlier phase), which is not directly proven in this pass

Safest practical sequence from code evidence alone:

  1. Reach the gate-write condition (hidden flag off, runtime-header state 3) so psx_debug_extra_channel_gate is latched.
  2. Arm/reactivate hidden-passcode state (psx_hidden_passcode_flag != 0).
  3. Trigger decoded input code 0x1e to call psx_debug_grant_weapon_channels_and_ammo.
  4. Helper runs normal grants through 0x0c, then executes extra 0x0d unlock because gate is already set.

Confidence for this sequence shape: high (~0.86) from direct write/read disassembly and guarded call flow; medium (~0.63) on exact player-facing button/passcode choreography for each phase.

Special passcode priming closure (?0SR / ?RTN / ?QQQ) (2026-04-11 live pass)

Scope of this pass was to answer one specific question: can one of the recovered special passcodes (?0SR, ?RTN, ?QQQ) by itself prime psx_level_runtime_header_state or psx_debug_extra_channel_gate so JL-9 enable is one-code, or does executable evidence still force a two-code flow.

Direct decode/write facts (all from psx_passcode_decode_to_mission_selector, 0x8003ec8c):

  1. ?RTN branch (special index 0x0f) writes psx_level_runtime_header_state = 0 at 0x8003ed10.
  2. ?0SR branch (special index 0x10) writes psx_hidden_passcode_flag = 1 at 0x8003ed28.
  3. ?QQQ branch (special index 0x11) returns sentinel 0x12 and does not write psx_hidden_passcode_flag or psx_level_runtime_header_state in that branch.
  4. All three special branches bypass the normal first-char delta calculation path that writes runtime-header state in the non-special lane (0x8003ed3c..0x8003ed58).

Direct gate-arm facts (from psx_set_debug_extra_channel_gate, 0x800230e4 / store at 0x800232f0):

  1. psx_debug_extra_channel_gate is set to 1 only when both are true at that branch:
  • psx_hidden_passcode_flag == 0 (0x800232d0 guard)
  • psx_level_runtime_header_state == 3 (0x800232e4 guard)
  1. This gate is read later at 0x8002fff4 in psx_debug_grant_weapon_channels_and_ammo for the extra unlock(0x0d) branch.

Implication for one-code versus two-code:

  • None of ?0SR / ?RTN / ?QQQ can by itself satisfy the gate-arm condition for JL-9 extra unlock:
    • ?RTN forces header state to 0, not 3.
    • ?0SR forces hidden flag to 1, but gate arm requires hidden flag 0.
    • ?QQQ does not set header state to 3 and does not arm the gate by itself.
  • So the strongest executable-backed result remains a two-phase flow where gate arm must come from a separate pre-hidden state, then hidden mode is armed for the input-triggered grant.

Strongest exact passcode sequence currently provable:

  1. Prime phase: use a non-hidden passcode/state path that leaves psx_hidden_passcode_flag==0 and reaches psx_level_runtime_header_state==3 before the 0x800232f0 gate-arm check runs.
  2. Hidden phase: enter ?0SR (canonical L0SR when selector is 0) to set psx_hidden_passcode_flag=1.
  3. Trigger phase: execute input code 0x1e to enter psx_debug_grant_weapon_channels_and_ammo; with gate latched, helper executes extra unlock(0x0d).

Practical status of the sibling specials in this model:

  • ?RTN: anti-prime for JL-9 gate arm (explicitly clears runtime-header state).
  • ?QQQ: neutral/side-effect-special in this context (no recovered direct gate-prime write).

Confidence:

  • high (~0.91) that recovered special passcodes do not directly prime JL-9 gate arm by themselves (direct branch/store evidence)
  • high (~0.88) that the strongest current model is two-phase rather than one-code for guaranteed 0x0d extra unlock
  • medium (~0.66) on which exact non-special prime code is used in real play, because this pass intentionally focused on the recovered special-code trio and gate predicates

Core proven facts

1. Weapon table identity is direct

  • weapon-definition table base is 0x8006466a
  • row stride is 0x26
  • JL-2 row is 0x80064832 (index 0x0c)
  • JL-9 row is 0x80064858 (index 0x0d)

Recovered visible order remains:

  1. INVALID GUN
  2. RP-16
  3. RP-22
  4. RP-32
  5. SG-A1
  6. AC-88
  7. PA-31
  8. EM-4
  9. PL-1
  10. UV-9
  11. GL-303
  12. AR-7
  13. JL-2
  14. JL-9

The strongest stable row split is:

  • shared: +0x1c = 0x18, +0x23 = 0x0e
  • diverged: JL-2 +0x24 = 0x4b, JL-9 +0x24 = 0x0f

1b. Compact local id to weapon row id conversion is table-driven

The runtime values near selected/inventory state (0x0001, 0x0002, 0x000b, 0x000c) now reconcile cleanly with the weapon table as follows:

  1. Callers pass a compact channel/local code to psx_apply_channel_effect_and_commit_selected_item_id (0x8002ef34) (for example at 0x8001d3fc).
  2. 0x8002f15c then loads the committed weapon id from channel_commit_row_selected_item_id[(channel*10)+9].
  3. That committed id is consumed as the weapon-table index in psx_weapon_def_get_u16_with_mode_gate (0x800315d8), which resolves idx*0x26 from base 0x8006466a.

This means the caller-side compact channel/local code to final selected weapon row-id step is implemented through channel-table lookup, not a dedicated global +1 conversion helper.

Concrete JL pair from this path:

  • compact local/channel 0x0b resolves through table slot +9 to committed row 0x0c (0x80064832, JL-2)
  • compact local/channel 0x0c resolves through table slot +9 to committed row 0x0d (0x80064858, JL-9)

This matches the stronger user-verified selected-weapon byte mapping where 0x0c = JL-2 and 0x0d = JL-9 at 0x8014577e.

Correction for domain clarity:

  • the user-verified selected-weapon byte mapping (00 none, 01 RP-16, 02 RP-22, 03 RP-32, 04 SG-A1, 05 AC-88, 06 PA-31, 07 EM-4, 08 PL-1, 09 UV-9, 0A GL-303, 0B AR-7, 0C JL-2, 0D JL-9) is row-id-domain and aligns with weapon table row identities.
  • this does not contradict the 0x8002ef34 call path: it clarifies that the call argument and the committed/stored selected id can be from different domains linked by the channel_commit_row_selected_item_id lookup.

2. Extra late unlock maps to JL-9

The strongest current chain is direct and no longer only inferential:

  1. psx_apply_channel_effect_and_commit_selected_item_id (0x8002ef34) commits the selected item id from channel_commit_row_selected_item_id.
  2. the channel 0x0d row commits item id 0x0d.
  3. weapon-definition helpers index rows as 0x8006466a + idx*0x26.
  4. row 0x0d is 0x80064858, whose visible name bytes are JL-9.
  5. row 0x0c is 0x80064832, whose visible name bytes are JL-2.

This closes the late extra lane as:

  • 0x0c = JL-2
  • 0x0d = JL-9

Upstream producer structure recovery for 0x800214ac (2026-04-11 live pass)

Scope of this pass was restricted to upstream production of the action-record pointer bytes consumed by psx_level_gate_slot_dispatch_from_action_record (0x800214ac).

Best recovered producer structure (current strongest model)

Recovered chain in live SLUS_002.68:

  1. psx_behavior_subopcode_dispatch (0x80027ecc) calls subop handler table entry with:
  • context argument from local_60[10]
  • action-record pointer frame from local_60+1
  1. psx_level_gate_slot_dispatch_from_action_record (0x800214ac) receives that frame as record and consumes:
  • record+0x00 pointer -> mode byte (*(*(record+0x00)))
  • record+0x04 pointer -> slot byte (*(*(record+0x04)))
  • record+0x08 pointer -> arg1 byte (*(*(record+0x08)))
  • record+0x0c pointer -> arg2 byte (*(*(record+0x0c)))
  1. Upstream per-argument pointer production occurs in psx_object_behavior_opcode_dispatch (0x8002677c):
  • if argument mask bit is clear, argument word is used as direct pointer
  • if set, argument word is treated as slot index and resolved by base + index*4
  • resolver base is object-local slot table (*(object_state+0x18)), except when mask 0x100 forces global base DAT_800789e0

This makes the producer structure a pointer-vector frame (local_60) populated by opcode-dispatch argument resolution, then forwarded to subop handler 49 (0x800214ac) through psx_behavior_subop_handler_table.

Concrete authored-data clue (bounded but real)

The sink tuple still closes as (slot,arg1,arg2)=(0x0f,0x0a,0x04) for the gate arm branch, but upstream formation is now better constrained:

  1. These values are not required to be hardcoded literals in executable code.
  2. They are produced as dereferenced bytes via pointer fields in the behavior action-record frame.
  3. Because each pointer field can be either direct or slot-index-resolved (base + index*4), authored behavior script data can feed this tuple either as direct byte pointers or as slot-table indices resolved at runtime.

This is the strongest concrete authored-data clue recovered in this pass: the tuple is behavior-script produced pointer data, not a fixed immediate triple in the sink function.

Exact Ghidra changes applied in this pass

  1. Function rename:
  • 0x800268a4: FUN_800268a4 -> psx_behavior_arg_index_to_slot_ptr
  1. Decompiler comments:
  • 0x80026814: argument resolver semantics (index*4 pointer mapping and base selection)
  • 0x80027f0c: producer frame field map (local_60[0..4] plus context at local_60[10])
  • 0x800215cc: sink-side field mapping (record+4/+8/+0c to slot/arg1/arg2)

Conservative confidence split:

  • high (~0.9) on pointer-frame field mapping and resolver behavior (direct decompile evidence)
  • medium (~0.65) on exact authored object/mission row currently producing (0x0f,0x0a,0x04) in shipped gameplay without runtime trace

3. Hidden passcode path reaches the JL-9 lane

The strongest recovered control path is:

  1. psx_passcode_decode_to_mission_selector (0x8003ec8c) sets psx_hidden_passcode_flag
  2. psx_object_update_runtime_input_modes checks psx_hidden_passcode_flag
  3. on gated input code 0x1e, it calls psx_debug_grant_weapon_channels_and_ammo
  4. that helper always unlocks through 0x0c and conditionally unlocks 0x0d when psx_debug_extra_channel_gate != 0

This is the strongest current executable-backed explanation for how the extra PSX-only lane reaches JL-9.

The newest narrow passes now sharpen the trigger story in a more useful way:

  • code now clearly supports hidden passcode active -> gated input code 0x1e -> psx_debug_grant_weapon_channels_and_ammo -> extra 0x0d unlock behind psx_debug_extra_channel_gate
  • input-map closure now ties 0x1e to pad mask 0x2800, which is high-confidence R1 + Circle under the active digital-pad bit layout
  • the remaining JL-9 trigger uncertainty is no longer the chord; it is the exact pre-hidden player-visible action that reaches the gate-arm writer path at 0x800232f0

4. JL-2 is now the stronger normal ammo-lane unknown

The current practical split is:

  • JL-2 sits in ordinary acquisition space (0x0c)
  • JL-9 sits in the extra hidden/debug-conditioned late lane (0x0d)
  • the visible string JL-2 AMMO exists at 0x800642b6
  • no matching plain JL-9 AMMO string has been recovered in the same string family

That does not fully decode JL-2, but it does shift the next unknown toward JL-2 rather than back toward JL-9 existence proof.

5. The checked dump is VRAM, not slot RAM

The user-supplied binary/Crusader - No Remorse Memdump Weapons.bin is now best read as a PSX VRAM capture:

  • file size is exactly 0x100000 (1,048,576) bytes, matching PlayStation VRAM size
  • the dump contains VRAM-like repeated 16-bit pixel patterns rather than compact inventory rows
  • inspection found HUD and icon-atlas style regions, but not a direct weapon-slot RAM structure

Current safest conclusion:

  • the dump is useful as a presentation artifact
  • it is not a direct source for weapon-slot storage values
  • it does not currently distinguish JL-2 from JL-9 with confidence

6. The checked 2 MiB main RAM dump is plausible RAM, but still inconclusive

The user-supplied binary/Crusader - No Remorse Weapons Main Ram.bin is not rejected as the wrong broad domain.

Current safest read:

  • file size is 0x200000 (2,097,152) bytes, matching PlayStation main RAM size
  • no plain ASCII JL-2, JL-9, or JL- strings were recovered in the dump
  • no immediately self-identifying weapon-slot table was recovered from this pass alone

So the main-RAM dump currently lands in a narrower state than the VRAM dump:

  • it is plausible runtime state memory,
  • but not yet a self-decoding proof of whether the selected JL-? weapon with 10 clips and 0 loaded bullets is JL-2 or JL-9,
  • because the identity still appears to depend on numeric runtime ids that must be cross-referenced back to the executable-side weapon table and HUD/name resolver logic.

The newest inventory-oriented pass did at least tighten where to look next inside that dump:

  • candidate compact 16-byte slot-like records were noticed very early in the file (for example around file offsets 0xA0, 0xB0, 0xC0)
  • those records are not self-labeled and were not enough on their own to map RP-32 ... JL-?
  • the most useful next RAM step is now to map one confirmed executable-side inventory or HUD pointer onto the dump, instead of continuing a blind whole-image sweep

The starter-only compare forces a narrower and more defensible RAM read:

  • file offset 0x1456fc does still begin a compact sequence of 8-byte records whose first 16-bit field climbs 0x0002 .. 0x000b
  • however, the same sequence is unchanged in the starter-only dump, so it cannot be the live owned-weapon inventory list
  • the dynamic block starts immediately after that static table, at 0x14574c, and remains changed through at least 0x1457d0
  • the strongest current field closure inside that block is:
    • byte 0x14577e = 0x0c in the all-weapons dump
    • byte 0x14577e = 0x02 in the starter-only dump
    • executable-side write path now supports this as selected/committed weapon row-id state inside a player/runtime state block
  • 0x67944 separately changes from 0x0000000b to 0x00000001, but this field still lacks direct static ownership in the current image and should not be treated as a closed selected-weapon home yet

Current strongest practical consequence:

  • the earlier 0x145744 -> 0x000c / 0x67944 -> 0x0000000c patch recommendation is retracted
  • current evidence supports a channel-state ownership model plus a dynamic current-weapon row field at 0x14577e, not a contiguous owned-id inventory list that can be safely patched in place from this evidence alone
  • the next RAM step should target ownership-state bytes/flags in the dynamic runtime block and the channel-runtime block model, not the static 0x1456fc table

The latest follow-up also narrows one false lead and one stronger lead inside the same dump:

  • the candidate commit-table field at DAT_80064355[(channel*10)+9] does not show plain 0x0c / 0x0d values in the sampled rows of this dump, so that exact byte is not acting like a direct final JL row id here
  • however, denser table-like clusters of 0x0c / 0x0d do appear much later in RAM, especially around file offsets 0x133000 and nearby 0x133416 / 0x1335d4, which are now stronger candidates for real runtime weapon-slot or inventory-state structures than the earlier blind slot guess

The newest pass tightened those late clusters one step further:

  • the 0x133000 neighborhood now looks more like a compact fixed-record runtime table than random data, with the strongest candidate stride landing near 0x40
  • values in that area are dominated by small enum-like bytes such as 0x0d and 0x0f, which fit a runtime index/flag table better than text or raw pixel data
  • one additional pass also found an alternative clue region around 0x422c..0x4440 where triplet-like patterns such as 0x0c 0x0a 0x00 could plausibly encode a JL-2 / 10 clips / 0 loaded style state, but that interpretation is not yet strong enough to replace the 0x133000 cluster as the main RAM lead
  • the remaining blocker is now very specific: one executable-side HUD/inventory anchor still has to be tied to one of these RAM regions before the dump can be read as a confirmed runtime weapon list

Current best practical consequence:

  • no ask-user re-dump was forced from this pass, because the file does look like the right broad memory domain,
  • but a stronger runtime closure still needs either a more targeted live memory capture around the player/inventory struct or a resolved id->name/UI chain inside the executable.

Evidence detail

Weapon table and row consumers

  • psx_weapon_def_get_u16_with_mode_gate (0x800315d8) computes idx*0x26 and reads row fields from the 0x8006466a family
  • psx_weapon_def_apply_spawn_profile_by_index (0x8003d02c) fans row fields into live-named globals:
    • psx_weapon_spawn_type
    • psx_weapon_spawn_audio_event_id
    • psx_weapon_spawn_state_selector
  • current best art read stays narrow:
    • JL-2 and JL-9 share base type/art lane via +0x1c = 0x18
    • they diverge on selector/state lane via +0x24 = 0x4b vs 0x0f

Channel commit and HUD presentation

  • channel_commit_row_table is the per-channel commit source table

  • committed_selected_item_id mirrors the committed item id after 0x8002f15c

  • psx_hud_draw_selected_item_tile_bar (0x800424ac) is the strongest currently named HUD-side consumer lane

  • psx_ui_color_cycle_state, psx_hud_selected_item_color, psx_hud_selected_tile_color_a, and psx_hud_selected_tile_color_b now cover the nearby UI-tail state that was previously left as raw DAT_ labels

  • latest pass also tightened the selected/equipped chain with fresh comments at 0x8002ef34, 0x8002f15c, 0x800315d8, 0x8003d02c, and 0x800424ac, plus explicit extra-unlock naming at 0x80030004 -> psx_weapon_channel_unlock_and_seed_markers

  • latest pass also renamed the known writer for the extra late unlock gate: 0x800232f0 -> psx_set_debug_extra_channel_gate

  • the newest input-side pass also renamed 0x8001E37C -> psx_handle_special_input_code, which is now the strongest current upstream candidate for consuming the special 0x1e debug-trigger code range

  • the newest ammo-side pass also tightened the marker/runtime helper family with live names such as psx_marker_channel_mode_is_enabled, psx_marker_channel_get_mode_step_value, psx_marker_channel_runtime_state_snapshot, psx_marker_channel_runtime_state_restore, and psx_marker_channel_dispatch_mode_action, which are now the strongest nearby candidates for the actual ammo/count mutation lane even though the exact clips versus loaded bullets fields are still not closed

  • Evidence note: no alternate per-slot source was found — HUD item id is resolved via the channel commit table (DAT_80064355[(channel*10)+9]) -> committed_selected_item_id -> weapon-definition table at 0x8006466a -> psx_weapon_def_apply_spawn_profile_by_index -> HUD draw.

This lane helps explain what the HUD is drawing, but it is still weaker than the channel-commit chain for direct JL-2 vs JL-9 identity.

Confirmed resolver chain (evidence-backed):

  • commit source: DAT_80064355[(channel*10)+9] is loaded at 0x8002f15c and supplies the committed item id for a channel.

  • id -> row resolver: psx_weapon_def_get_u16_with_mode_gate (0x800315d8) computes idx * 0x26 and indexes the weapon-definition table at 0x8006466a to select the weapon row.

  • row -> display: psx_weapon_def_apply_spawn_profile_by_index (0x8003d02c) fans row fields into spawn/art/selectors consumed by HUD code; psx_hud_draw_selected_item_tile_bar (0x800424ac) consumes those selectors to draw the on-screen tile/art. Name/label bytes live in the weapon row and are therefore resolved implicitly by the idx*0x26 table access rather than a separate string-lookup UI helper.

    • 0x8003ec8c -> psx_passcode_decode_to_mission_selector
    • 0x80021138 -> psx_passcode_apply_mission_selector_and_stage
  • transformed compare tables:

    • 0x80064bbc
    • 0x80064bd0
    • 0x80064be4
    • 0x80064bf8
  • key hidden decoder behavior:

    • hidden index 0x0f: writes psx_level_runtime_header_state = 0, returns selector 0x10
    • hidden index 0x10: writes psx_hidden_passcode_flag = 1, returns selector 0
    • hidden index 0x11: returns selector 0x12

Current safest read is that the hidden compare path is real and transformed/table-driven, even though the exact folklore string mapping is still not fully closed at runtime.

Current interpretation

What is now solid

  • JL-9 is real
  • JL-9 is the extra late hidden/debug lane
  • JL-2 is the neighboring ordinary lane and now has stronger explicit ammo-label evidence
  • the checked dump is VRAM, not direct slot RAM
  • the checked 2 MiB main RAM dump is plausible runtime RAM, but still needs executable-side id resolution before it can close JL-2 vs JL-9

What is still open

  • exact runtime timing for psx_hidden_passcode_flag and psx_debug_extra_channel_gate
  • exact role of the denser 0x0c / 0x0d RAM clusters near 0x133000, and whether one of them is the real live inventory/slot table
  • exact executable-side anchor that proves whether the main RAM lead is the 0x133000 cluster or the smaller 0x422c..0x4440 candidate region
  • direct UI label/display resolver that prints JL-9 from committed runtime state in one instruction chain
  • exact JL-2 ammo decrement/storage path
  • exact JL-9 sprite/frame identity
  • exact shipped-map placement for either JL row

Input code 0x1e button-chord closure (2026-04-11 pass)

Focused live MCP pass on active SLUS_002.68 to close the exact input-side decode path around:

  • psx_object_update_runtime_input_modes (0x80012c30)
  • psx_input_map_get_code_and_edge (0x8002adbc, renamed this pass)
  • psx_input_map_update_state_for_pad (0x8002abe0, renamed this pass)
  • psx_input_map_install_profile (0x80042ec4, renamed this pass)

Direct closure:

  1. local_14 in runtime input mode update is read from the input-map state table.
  • psx_object_update_runtime_input_modes calls psx_input_map_get_code_and_edge(1,&local_18,&local_14).
  • local_14 == 0x1e is the exact compare that gates the debug grant helper when psx_hidden_passcode_flag != 0.
  1. Input code resolution is exact-match against a 0x32-entry mask table.
  • psx_input_map_update_state_for_pad polls raw pad mask from FUN_80048d04 and scans 0x80090d34 + pad*0xfc for exact value equality.
  • matched index is written to current-code state (0x80090e24 + pad*0xfc) and returned by psx_input_map_get_code_and_edge.
  1. Input code 0x1e is mapped to pad mask 0x2800 in all recovered profile cases.
  • psx_input_map_install_profile writes map entries with psx_input_map_set_code_to_padmask(1, code, mask).
  • every switch branch in this function writes code 0x1e -> 0x2800.
  1. Using the same PSX digital mask legend implied by nearby known controls (0x08 start, d-pad nibble patterns, shoulder/face combos), 0x2800 resolves to R1 (0x0800) + Circle (0x2000).

Practical trigger statement now supported:

  • after hidden passcode state is active, the JL-9 debug grant path is triggered by the decoded special input code 0x1e, which maps to the controller chord R1 + Circle.
  • because map lookup is exact-match, extra simultaneously held buttons can produce a different code and miss the 0x1e gate.

Confidence:

  • high (~0.93) that 0x1e -> 0x2800 is the actual decode mapping in this executable.
  • high (~0.88) that 0x2800 corresponds to R1 + Circle under the active digital-pad bit layout.

Selected-weapon-adjacent pointer reclassification (2026-04-11)

The user-reported pointer delta near the selected-weapon watch block is now closed as render-state, not inventory state:

  • all-weapons dump: pointer-like value 0x80078248
  • starter-only dump: pointer-like value 0x800782b8

Live MCP decompile/xref evidence on SLUS_002.68 shows these are the two double-buffer draw-environment records:

  • psx_platform_init (0x80042b38) initializes them with SetDefDrawEnv((DRAWENV *)&DAT_80078248, ...) and SetDefDrawEnv((DRAWENV *)&DAT_800782b8, ...)
  • the paired display environments are at 0x800782a4 and 0x80078314 (+0x5c from each draw-env base), matching a packed per-buffer draw/disp environment layout
  • DAT_80067954 is the active draw-env pointer toggled each frame in:
    • psx_present_frame_and_flip (0x80044188)
    • render_reset_draw_state (0x80042be8)
    • FUN_800461d0 (0x800461d0)

Practical consequence:

  • the observed 0x80078248 <-> 0x800782b8 change is a frame-buffer/render flip artifact
  • this pointer pair does not encode selected weapon id, HUD selected-item row, or inventory ownership state
  • weapon selection/inventory evidence remains stronger in the channel-commit and weapon-definition lanes (DAT_80064355, committed_selected_item_id, 0x8006466a + idx*0x26)

Runtime block classification around 0x8014574c..0x801457d0 (2026-04-11)

Current strongest executable-backed interpretation for the user-targeted RAM block:

  • this region is most likely a live player-object nested runtime-state block, not a HUD color cache and not the static channel-commit table
  • the watched byte at file offset 0x14577e (RAM 0x8014577e) is most strongly the active selected weapon row-id byte within the nested +0x1c runtime field family
  • this byte behaves like current-weapon metadata, not ownership-bitset storage and not direct ammo-count storage

Concrete evidence chain:

  1. psx_apply_channel_effect_and_commit_selected_item_id writes the committed item id from channel_commit_row_selected_item_id[(channel*10)] into two sinks:
  • global committed_selected_item_id (0x80078a90)
  • nested player runtime field at *( *( *(DAT_800789f8 + 8) + 0x18) + 0x1c )
  1. psx_set_debug_extra_channel_gate clears that same nested +0x1c field in several mode-reset branches, alongside nearby nested fields (+0x14, +0x16, low byte of +0x18), consistent with runtime weapon-mode/state reset behavior.
  2. psx_object_create_simple_record initializes the nested block rooted at *(obj_component + 0x18) as a large runtime state area and stores back-pointer/object runtime linkage there, matching a heap-resident per-object state block.
  3. The dump delta at byte 0x14577e (0x0c all-weapons vs 0x02 starter-only) fits this as a selected weapon row-id change rather than ownership-table cardinality or HUD palette animation.

Interpretation confidence:

  • 0x14577e as selected weapon row-id byte (nested +0x1c field family): high
  • broader 0x8014574c..0x801457d0 region as player nested runtime-state block: medium-high
  • region as primary inventory ownership table: low
  • region as primary ammo counters table: low
  • region as HUD-only cache: low

Starter-only compare correction (2026-04-11)

The follow-up compare against binary/Crusader - No Remorse RAM starter weapon only.bin corrects one earlier overreach in this note.

What changed:

  • 0x1456fc..0x145748 is identical in the all-weapons and starter-only dumps, even though the observed weapon inventory is very different
  • therefore that attractive 0x0002 .. 0x000b 8-byte record sequence is a static table or static-adjacent runtime seed, not the live owned-weapon list
  • the real dynamic lane starts at 0x14574c and includes:
    • 0x14577e: 0x000c -> 0x0002
    • nearby count/flag-like deltas around 0x14575c..0x14578c
  • the separate watched field at 0x67944 also changes cleanly (0x0000000b -> 0x00000001), but the current image still has no recovered static xrefs for 0x80067944

Current safest wording after that compare:

  • no contiguous owned-weapon id list has been recovered yet
  • the storage model is better explained by channel-state ownership plus one or more dynamic current-weapon fields in the runtime block
  • the earlier patch-target recommendation on 0x145744 should be considered withdrawn

Speculation and folklore

Keep speculation bounded here rather than mixing it into the evidence chain.

Current plausible but not fully closed reads:

  • L0SR remains the strongest current executable-backed cheat-mode candidate because it fits the recovered four-symbol hidden branch better than L0SER
  • R1 + Circle when L0SR is active is now the strongest executable-backed button-chord read for the gated input route; the remaining folklore-level uncertainty is about the exact pre-hidden gate-arm action, not the 0x1e chord mapping itself
  • XXXX still plausibly maps to the hidden-pictures folklore because the decoder has a second hidden-special lane consistent with a transformed secret path
  • the VRAM dump likely contains the weapon HUD/icon atlas, but without labeled reference icons or a cleaner runtime capture it does not yet let us separate JL-2 from JL-9 visually
  • the main RAM dump likely contains the relevant runtime inventory state, but current evidence is split between a nearby commit-table neighborhood that does not show plain final JL ids and stronger later clusters around 0x133000 that still need one executable-side anchor to decode
  • the strongest current input-side static closure now turns the special 0x1e code into pad mask 0x2800 and a high-confidence R1 + Circle interpretation, while still leaving the upstream pre-hidden gate-arm producer unresolved

None of those points are needed for the current core conclusion that the extra late executable-backed lane is JL-9.

Best next steps

  1. Capture a runtime sample after the hidden passcode/debug gate is active and log which weapon channels are actually unlocked, especially 0x0d.
  2. Decode the controller-button mapping that produces the gated input code reaching psx_debug_grant_weapon_channels_and_ammo, so the L0SR trigger path is either confirmed or corrected.
  3. Trace all reads and writes of psx_debug_extra_channel_gate to close the late extra-unlock precondition.
  4. Trace the direct JL-2 AMMO UI path and the underlying ammo decrement/storage lane.
  5. Decode the denser 0x0c / 0x0d RAM clusters around 0x133000 using one confirmed executable-side inventory/HUD anchor, instead of continuing whole-image sweeps.
  6. If that anchor still does not land cleanly, test the smaller 0x422c..0x4440 candidate region against the same runtime meaning before requesting another dump or capture.

Weapon acquisition systems vs id 0x0d / 0x01 (2026-04-11 broad pass)

Focused live MCP pass on active SLUS_002.68 to classify which acquisition families can produce the verified selected ids JL-9=0x0d and RP-16?=0x01.

Systems analyzed

  • default loadout/init: psx_weapon_channels_init_mode_loadout (0x8002f814)
  • mission transition mode application: psx_weapon_channels_apply_mode_transition_state (0x8002f278)
  • pickups/action-coded grants: accepted-code dispatch lane at 0x8001d3fc
  • shop path: psx_weapon_shop_try_apply_entry (0x8003de68) using DAT_80064B90
  • scripted awards: psx_section0_dispatch_root_apply_packed_channel_actions (0x800311f4)
  • debug grants: psx_debug_grant_weapon_channels_and_ammo (0x8002fd90)

Concrete results

  1. id 0x0d (JL-9) remains gated from ordinary loadout/shop paths.
  • loadout init has fixed unlock/ammo calls through 0x0c and does not directly unlock 0x0d
  • mission transition state helper does not add a 0x0d direct unlock path
  • shop front path (param<10) maps through DAT_80064B90[0..9] = 03 04 05 06 07 08 09 0a 0b 0c, so no direct 0x0d there
  1. id 0x0d is directly produced by debug grant and is possible in data-driven scripted/pickup lanes.
  • debug: 0x8002fff4 conditionally calls psx_weapon_channel_unlock_and_seed_markers(0x0d) only when psx_debug_extra_channel_gate != 0
  • scripted awards: packed-action decoder (0x80031250) calls unlock(channel) from seeded DAT_8008F120 rows (runtime-seeded from section0 markers)
  • pickup/action-coded lane (0x8001d3fc) range-check path still commits accepted codes through psx_apply_channel_effect_and_commit_selected_item_id(a0), including 0x0d
  1. id 0x01 is not excluded and appears in non-debug systems.
  • shop table byte dump confirms DAT_80064B90 = 03 04 05 06 07 08 09 0a 0b 0c 01 05 04 03 02 07
  • while 0x01 is outside the first ten direct-unlock shop slots, it is still present in shop's alternate branch (param_1=10) via the same channel map
  • pickup/action-coded commit path accepts low ids (including 0x01) and routes them to commit
  • scripted packed actions are data-driven and can target 0x01 when authored in section0 records

Exclusion read after this pass

  • 0x0d: strongest current read is "not part of baseline ordinary progression," with explicit debug-gated production and only data-driven/scripted/pickup exceptions.
  • 0x01: not excluded; it appears in normal non-debug content paths (shop mapping plus accepted low-id action path).

Notes on evidence quality

  • The packed-action LUT base (0x8008F120) is runtime-seeded and can be uninitialized in static reads; classification therefore uses the seeding/writer and decoder call behavior, not a static full table dump.
  • The core 0x0d gate remains directly evidenced at 0x8002fff4 in live SLUS_002.68.

Appendix: key anchors

Weapon rows and helpers

  • 0x8006466a weapon-definition table base

  • 0x80064832 JL-2 row (0x0c)

  • 0x80064858 JL-9 row (0x0d)

  • 0x800315d8 psx_weapon_def_get_u16_with_mode_gate

  • Live field map (evidence): row+0x20 is read as a 16-bit row field (loaded at 0x8003160c), row+0x22 is a 1-byte mode/gate field (loaded at 0x8003163c), and row+0x24 contains a per-row state/selector used by HUD/ammo resolver (example: JL-2 shows 0x4B, JL-9 shows 0x0F). The nearby ASCII JL-2 AMMO at 0x800642b6 aligns with code reads from the 0x800642b2/0x800642b4 region, supporting a small adjacent string/table used for ammo text resolution.

  • 0x8003d02c psx_weapon_def_apply_spawn_profile_by_index

Channel commit and HUD

  • 0x8002ef34 psx_apply_channel_effect_and_commit_selected_item_id
  • 0x8002f15c committed item-id load from channel commit row
  • 0x800424ac psx_hud_draw_selected_item_tile_bar
  • 0x800350a8 psx_render_mode_dispatch

Hidden passcode and extra unlock

  • 0x80034e38 psx_passcode_screen_eval_current_entry
  • 0x8003ec8c psx_passcode_decode_to_mission_selector
  • 0x80013154 psx_object_update_runtime_input_modes
  • 0x8002fd90 psx_debug_grant_weapon_channels_and_ammo
  • 0x800232f0 psx_set_debug_extra_channel_gate
  • 0x80030004 extra 0x0d unlock call site

Runtime dump artifacts

  • binary/Crusader - No Remorse Memdump Weapons.bin -> VRAM-sized HUD/presentation artifact
  • binary/Crusader - No Remorse Weapons Main Ram.bin -> 2 MiB plausible main RAM artifact, currently not self-decoding enough to close JL-2 vs JL-9
  • stronger current unresolved RAM cluster candidates for live weapon-slot state: around file offsets 0x133000, 0x133416, and 0x1335d4
  • secondary smaller RAM candidate for JL-2 / 10 clips / 0 loaded style state: around file offsets 0x422c..0x4440

Live-named globals used in this note

  • 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
  • psx_weapon_spawn_state_selector
  • psx_ui_color_cycle_state
  • psx_hud_selected_item_color
  • psx_hud_selected_tile_color_a
  • psx_hud_selected_tile_color_b

Hidden passcode decode closure (2026-04-11 pass)

Focused live MCP pass on active SLUS_002.68 against:

  • psx_passcode_screen_eval_current_entry (0x80034e38)
  • psx_passcode_decode_to_mission_selector (0x8003ec8c)
  • passcode tables at 0x80064bbc, 0x80064bd0, 0x80064be4, 0x80064bf8
  • helper psx_passcode_generate_encoded_quad (0x8003ec30)

Closed transform logic

Decode path (0x8003ec8c) compares transformed input, not direct ASCII:

  1. entry[1..3] are normalized by -0x1b and matched against table triplets:
  • T1 = [0x80064bd0 + i]
  • T2 = [0x80064be4 + i]
  • T3 = [0x80064bf8 + i]
  • valid i range is 0..0x11 (18 entries)
  1. If no i matches all three, decode fails.
  2. For ordinary entries (i != 0x0f/0x10/0x11):
  • delta = entry[0] - (0x1b + [0x80064bbc + i])
  • psx_level_runtime_header_state = delta (stored at 0x80068ab0)
  • accept only when delta < 4; return selector i+1

Special branches:

  • i == 0x0f: clears psx_level_runtime_header_state and returns 0x10 (delay-slot flow preserves v0=0x10)
  • i == 0x10: sets psx_hidden_passcode_flag = 1 (0x80067454) and returns 0
  • i == 0x11: returns sentinel 0x12

Important closure: all three special branches bypass the first-character delta check, so their first character is effectively wildcarded by the decoder.

Recovered table values

Read directly from 0x80064bb0..0x80064c20:

  • T0 (0x80064bbc) = 01 03 0B 0E 0F 06 07 0A 09 12 01 02 03 03 06 08 08 12
  • T1 (0x80064bd0) = 10 11 08 13 02 15 16 17 18 19 1A 1B 1C 04 03 0D 14 12
  • T2 (0x80064be4) = 0B 0C 0D 0A 19 00 01 02 03 04 05 06 07 08 09 0F 0E 12
  • T3 (0x80064bf8) = 02 0B 0C 03 0E 0F 10 11 12 13 14 15 16 17 18 0A 0D 12

Symbol alphabet (0x80063ef0) for passcode glyph indexes is:

  • BCDFGHJKLMNPQRSTVWXZ0123456789

Concrete recovered hidden/debug strings

Generator (0x8003ec30) emits:

  • out[0] = T0[i] + psx_level_runtime_header_state
  • out[1] = T1[i]
  • out[2] = T2[i]
  • out[3] = T3[i]

Mapped through 0x80063ef0, special rows are:

  1. i=0x10 (sets psx_hidden_passcode_flag): ?0SR
  • canonical when selector state is 0: L0SR
  • decoder confirms first character wildcard for this branch
  1. i=0x0f (clears selector state): ?RTN
  • canonical when selector state is 0: LRTN
  • first character wildcard for this branch
  1. i=0x11 (sentinel return 0x12): ?QQQ
  • canonical when selector state is 0: XQQQ
  • first character wildcard for this branch

Debug JL-9 path tie-in

This pass tightens the two-phase gate model using direct branch conditions:

  1. Hidden branch (i=0x10) sets psx_hidden_passcode_flag (0x80067454) at 0x8003ed28.
  2. Input-mode handler (0x80013154) only calls debug grant (0x8002fd90) on input code 0x1e when hidden flag is nonzero.
  3. Extra JL-9 unlock (0x80030004, channel 0x0d) still requires psx_debug_extra_channel_gate != 0 at 0x8002fff4.
  4. Gate write (0x800232f0) still requires hidden flag zero plus psx_level_runtime_header_state == 3.

Current best practical read remains: extra JL-9 requires a staged hidden/debug flow; no single direct ordinary-lane unlock for 0x0d was recovered.

Shipped section0 slot-0x14 closure (2026-04-11 pass)

Focused live MCP + scene-cache pass on active SLUS_002.68 to answer one narrow question: can shipped non-debug authored section0/script bytes ever drive the unlock(0x0d) lane used by JL-9.

Executable closure

  1. Packed-action dispatch behavior is explicit at psx_section0_dispatch_root_apply_packed_channel_actions (0x800311c4):
  • decoder uses slot = (action_byte & 0x3f) into seeded triplets DAT_8008f120/121/124
  • triplet kind 3 path calls psx_weapon_channel_unlock_and_seed_markers(channel)
  1. Seed mapping is explicit at psx_section0_dispatch_root_seed_marker_channel_table (0x8002f518):
  • psx_section0_dispatch_root_find_marker_record_by_channel(0x14, 3, 0x0d)
  • therefore slot 0x14 maps to action-kind 3, channel 0x0d
  1. Live naming cleanup applied:
  • 0x8002fd90 renamed to psx_debug_grant_weapon_channels_and_ammo to resolve the duplicate-name collision with 0x8002e5f0

Shipped authored-data closure

Cross-map scan of shipped PSX scene-cache root-dispatch records (.cache/scene-cache/psx-remorse/map-*/.../scene.json) parsed packed action dwords from record raw words and checked for bytes where (byte & 0x3f) == 0x14.

Results:

  • scanned root-dispatch records: 3298
  • slot-0x14 authored-byte hits: 119
  • maps with at least one slot-0x14 hit: 22
  • map ids with hits: 1,2,3,4,5,22,23,26,27,28,29,47,63,64,80,82,88,89,104,106,108,122

Representative shipped records:

  • map 1, item:21:psx-section0_dispatch_roots:left:8, raw words 0045 1d3b 0d43 0014 0000 0020 (packed dword 0x00140d43)
  • map 5, item:11:psx-section0_dispatch_roots:right:8, raw words 004e 1b83 0943 0014 0000 0020 (packed dword 0x00140943)
  • map 63, slot-0x14 hits present (2)
  • map 89, slot-0x14 hits present (30)

Practical verdict for JL-9 legit-path question

  • Ruled in: shipped non-debug section0/script authored data does include packed action bytes that resolve to slot 0x14, and executable seed/dispatch logic maps slot 0x14 to unlock(channel 0x0d).
  • Not fully universal: this does not prove every campaign route triggers those records during normal play timing, but it does prove the non-debug shipped-data lane exists and is executable-reachable in principle.

Confidence:

  • high (~0.93) that slot 0x14 is an unlock(0x0d)-capable packed-action slot in this build
  • high (~0.91) that shipped non-debug section0 root records contain authored slot-0x14 bytes
  • medium-high (~0.77) that this yields legitimate in-play JL-9 acquisition in at least some non-debug scenarios (remaining uncertainty is trigger/reachability timing, not byte-path existence)

Step-2 caller narrowing for gate-arm event (0x800230e4/0x800232f0) (2026-04-11 live pass)

Goal of this pass was to close the immediate caller/event side for the gate writer branch (param_2==0x0a, param_3==4) inside psx_set_debug_extra_channel_gate.

Exact executable evidence recovered

  1. The gate writer remains exactly sb v0,0x739d(at) at 0x800232f0 in psx_set_debug_extra_channel_gate (0x800230e4) under local branch param_2==0x0a and case param_3==4.
  2. Direct caller xrefs stay empty because this lane is indirect-dispatch based.
  3. A concrete indirect dispatcher block was recovered at 0x800214ac..0x800215f8:
  • 0x800215bc: compares dispatch index (a2) against per-level table byte DAT_80063e68[current_level].
  • 0x800215dc: loads handler pointer from jump table DAT_800640a0[a2] and jalrs.
  • 0x800215cc + 0x800215e0: handler arguments are loaded as bytes from pointer fields in the action record (a1=*(*(record+8)), a2=*(*(record+0xc))).
  1. Table slot proof:
  • DAT_800640a0[0x0f] = 0x800230e4 (the target handler).
  • therefore the gate-arm branch requires this record-level triplet at dispatch time:
    • dispatch index byte = 0x0f (to pick handler 0x800230e4),
    • first argument byte = 0x0a,
    • second argument byte = 0x04.
  1. Per-level gating proof from DAT_80063e68:
  • indices where table byte is 0x0f: 54,55,56,57,58,82.
  • outside those level indices, this exact 0x0f -> 0x800230e4 lane is not selected by the recovered level gate compare.

Inferred player-facing meaning (narrowed)

  • strongest read is now a late/level-scripted control-event lane, not a random global input callback: the branch is reached through a level-gated action dispatcher and a control-handler jump table.
  • practical narrow set: player-visible triggers tied to scripted/control progression events in level-index set {54..58,82} that emit dispatch triple (0x0f,0x0a,0x04).
  • still not fully singular: static analysis in this pass did not recover one uniquely named UI/menu function boundary that can be asserted as the only producer of that exact triple.

Confidence update

  • high (~0.90) that call reachability into 0x800230e4 is through 0x800214ac..0x800215f8 + DAT_800640a0 indirect dispatch.
  • high (~0.87) that branch trigger shape is exactly (dispatch_index=0x0f, param_2=0x0a, param_3=0x04).
  • medium-high (~0.76) that player-facing source is a small late-level scripted/control action family (54..58,82), not a single global menu button.

Live Ghidra artifacts changed

  • conservative disassembly comments added:
  • 0x800215bc (level-gated dispatch compare against DAT_80063e68[current_level])
  • 0x800215cc (argument-byte extraction from action record pointers)
  • 0x800215dc (jump-table dispatch through DAT_800640a0, slot 0x0f -> 0x800230e4)

Mission 16 / developer office mapping correction (2026-04-11 live pass)

An older interpretation in this note treated the ?RTN / NRTN special branch as if it directly selected DAT_80063e54[0x0f] = 0x36, which would have put Mission 16 / dev-office content inside the DAT_80063e68 == 0x0f gate family.

That interpretation is now superseded.

Correct runtime flow

  1. The ?RTN special branch is decode index i = 0x0f, but it does not return 0x0f.
  • it clears psx_level_runtime_header_state at 0x8003ed10
  • due to the decoder's return flow, raw selector returned to caller is 0x10
  1. psx_passcode_screen_eval_current_entry then maps through DAT_80063e54[0x10], not DAT_80063e54[0x0f].
  • live table bytes now anchor this as DAT_80063e54[0x10] = 0x3f
  1. Caller state at 0x80034d84 therefore uses mapped s0 = 0x3f, not 0x36.
  2. Apply path still reaches selector-0x10 case at 0x8002142c, which writes DAT_800675e4 = 0x1d, sets DAT_80067379 = 2, and calls the common helper with a0 = 0x36.

Practical consequence for the dev-office hypothesis

  • a0 = 0x36 in the selector-0x10 apply case may still be related to the user-observed developer-office lane, but that is not the same claim as current_level_map_id == 0x36 or DAT_80063e68[current_level] == 0x0f.
  • so the previously attractive chain ?RTN -> map 54 -> gate family 0x0f should be treated as withdrawn.
  • the surviving office hypothesis is weaker and narrower: ?RTN / NRTN may still route into office-like content, but current static evidence does not prove that this places the session directly inside the slot-0x0f gate-arm family.

Confidence update

  • high (~0.95) that the correct caller mapping is ?RTN index 0x0f -> raw selector 0x10 -> DAT_80063e54[0x10] = 0x3f
  • high (~0.91) that the older DAT_80063e54[0x0f] = 0x36 interpretation was the wrong table index for the runtime ?RTN path
  • medium (~0.66) that a0 = 0x36 in the apply case still corresponds to the same player-facing developer-office concept without one runtime label capture

Live Ghidra artifacts changed

  • conservative comments added in later follow-up:
  • 0x8003ed10
  • 0x80034d84
  • 0x8002142c

Step-2 producer-path closure (control-opcode lane) (2026-04-11 live pass)

Goal of this pass was to close the unresolved upstream producer for the gate-arm dispatcher block (0x800214ac..0x800215f8) by tracing backward from 0x800215cc / 0x800215e0 into the parent script/control lane.

Exact executable evidence recovered

  1. 0x800215cc / 0x800215e0 byte loads are inside unnamed control helper 0x800214ac.
  • it reads pointer arguments from record+8 and record+0xc (arg1=*(*(record+8)), arg2=*(*(record+0xc))), then dispatches through DAT_800640a0[slot] after level gate compare at 0x800215bc.
  1. 0x800214ac is now proven as table entry DAT_80063610[49] (address 0x800636d4).
  2. Upstream call chain for that entry is now explicit:
  • psx_object_behavior_opcode_dispatch (0x8002677c) dispatches through DAT_800641ac[opcode_index].
  • table entry DAT_800641ac[54] = 0x80027ecc.
  • 0x80027ecc performs a second dispatch by loading sub-opcode from *(*(a1+0)) and jumping through DAT_80063610[subop].
  • when subop==49, it reaches 0x800214ac.
  1. Therefore the unresolved writer tuple path is no longer a free-floating callback; it is a nested behavior/control opcode producer path:
  • behavior opcode index 54 -> secondary sub-opcode 49 -> 0x800214ac -> slot table DAT_800640a0.
  1. The gate-arm writer tuple remains unchanged at the sink:
  • slot 0x0f selects psx_set_debug_extra_channel_gate (0x800230e4), and branch write requires args 0x0a, 0x04.

What this closes and what remains open

  • closed: where the action-record bytes come from structurally.
  • they are produced from the object behavior/control stream argument pack used by opcode-54 handler 0x80027ecc and its sub-dispatch into DAT_80063610[49].
  • still open: one singular player-facing mission event label.
  • static executable evidence now identifies the producer subsystem and dispatch indexes, but does not yet uniquely name one authored script instance as "the" office event.

Narrowed map/event set in this pass

  • executable gate compare lane remains constrained by DAT_80063e68[current_level] == 0x0f (current narrowed level-id family from prior pass: {54..58,82}).
  • cache-side cross-check in this pass:
  • available scene-cache member from that family was map 82 only in current catalog snapshot.
  • map 82 section0 root records showed no direct slot-0x0f packed-byte hits, which is consistent with this pass's new upstream finding that the writer can be produced by the object control-opcode stream lane (not only section0 root packed-action bytes).

Practical player-visible status

  • status is now stronger than "unknown source":
  • this is a scripted in-level behavior/control opcode event lane (nested opcode 54 -> 49), i.e., a real gameplay/script progression path rather than a menu-only artifact.
  • but still not fully singular:
  • one concrete authored trigger instance (exact mission script/object instance) is still unresolved.

Confidence update

  • high (~0.94) that producer path is behavior opcode 54 -> sub-op 49 -> 0x800214ac -> slot 0x0f handler 0x800230e4.
  • high (~0.90) that action-byte arguments at 0x800215cc/0x800215e0 are sourced from opcode argument-pointer records, not direct UI input.
  • medium (~0.74) that a single map-82-like scripted control event family is the practical player-visible source, pending one final authored-instance bind.

Step-2 producer-path reachability correction (2026-04-11 live pass)

This follow-up rechecked whether the previously claimed opcode 54 -> sub-op 49 -> 0x800214ac chain is currently proven reachable in active SLUS_002.68.

Direct executable evidence from live script/ref scans

  1. DAT_800641ac does contain high-index entries:
  • DAT_800641ac[49] = 0x80027d2c
  • DAT_800641ac[54] = 0x80027ecc
  1. 0x80027ecc still dispatches through DAT_80063610[subop], and DAT_80063610[49] = 0x800214ac remains true.
  2. But static reachability in this image is currently narrower than that table topology suggests:
  • only recovered caller to psx_object_behavior_opcode_dispatch (0x8002677c) is at 0x80026740
  • that caller guards with (opcode_word - 1) < 0x0a at 0x80026710
  • therefore this known path only selects DAT_800641ac[0..9]
  1. 0x80027ecc currently has only one recovered incoming reference, and it is data-only (0x80064284 table slot), not a direct call/jump xref.
  2. 0x800214ac likewise currently has data-only incoming evidence (0x800636d4 table slot), not a direct call xref.

Practical correction

  • The earlier 54 -> 49 statement should now be treated as table-topology evidence, not yet as a proven active runtime producer path.
  • What remains high-confidence is the sink-side gate-writer shape:
  • 0x800214ac..0x800215f8 is a real level-gated slot dispatcher.
  • DAT_800640a0[0x0f] = 0x800230e4 and sink args remain 0x0f,0x0a,0x04.
  • The open upstream question is now more precise:
  • recover a second proven caller path into 0x8002677c (or alternate path into 0x800214ac) that can actually feed high dispatch indices.

Live Ghidra artifacts changed

  • conservative disassembly comments added:
  • 0x80026710 (known caller path bounds dispatch index to < 0x0a)
  • 0x8002685c (documents DAT_800641ac[a0] use and known caller-path bound)
  • 0x80027f0c (documents secondary DAT_80063610[subop] use under 0x80027ecc)

Confidence update (corrected)

  • high (~0.93) that sink-side slot dispatch into 0x800230e4 via DAT_800640a0[0x0f] is real.
  • high (~0.89) that current static refs do not yet prove active reachability of DAT_800641ac[54] -> 0x80027ecc from the known caller path.
  • medium (~0.71) that high-index behavior-opcode producers still exist in runtime via currently un-recovered caller/context lanes.

Natural gate-arm active-caller recheck (2026-04-11 live pass)

Scope of this pass was narrow and read-only: verify whether any currently proven gameplay caller path can really feed the sink-side dispatcher at 0x800214ac..0x800215f8 with the known tuple (slot 0x0f, arg1 0x0a, arg2 0x04).

New direct evidence

  1. Sink-side facts remain unchanged and strong:
  • 0x800214ac performs level-gated indirect slot dispatch through DAT_800640a0[a2].
  • slot 0x0f still resolves to 0x800230e4 (psx_set_debug_extra_channel_gate) from table entry address 0x800640dc.
  • sink arg bytes are still loaded as a1=*(*(record+8)) and a2=*(*(record+0xc)).
  1. Upstream topology is still true but only as topology:
  • 0x80027ecc is still table-backed at DAT_800641ac[54] (0x80064284).
  • 0x80027d2c is still table-backed at DAT_800641ac[49] (0x80064270).
  • 0x800214ac is still table-backed at DAT_80063610[49] (0x800636d4).
  1. Proven active gameplay caller lane into behavior-op dispatch remains bounded:
  • callers into psx_run_object_behavior_program_tick (0x80026690) include psx_object_integrate_motion_and_route_visible callsites (0x80012668, 0x800131d0) plus two additional runtime callsites (0x80011e78, 0x80011f78).
  • the dispatch handoff at 0x80026740 is still guarded by (opcode_word - 1) < 0x0a at 0x80026710.
  • therefore this proven gameplay lane can only select DAT_800641ac[0..9], not index 54.
  1. No additional direct caller/xref path into 0x8002677c, 0x80027ecc, or 0x800214ac was recovered in this pass.

Practical interpretation update

  • strongest active producer candidate is now the currently proven gameplay behavior-tick lane: psx_object_integrate_motion_and_route_visible -> psx_run_object_behavior_program_tick -> psx_object_behavior_opcode_dispatch.
  • but that lane currently cannot feed the 54 -> 49 -> 0x800214ac chain because of the < 0x0a guard.
  • so the older 54 -> 49 chain should stay in the model only as valid table topology, and be deprioritized as an immediate natural-event explanation until a second real caller/context is recovered.

New concrete static target

Next high-value target is to recover the missing caller/context lane that reaches psx_object_behavior_opcode_dispatch with indices above 9, or an alternate feeder that reaches DAT_80063610[49] directly.

Most concrete first pivot for that is the mode/transition hub around FUN_80020f7c (0x80020f7c) and its active callers (0x80022068, 0x80022e58, 0x80023080, 0x8002748c), because this is now the strongest live non-table-only control lane that already consumes level-family state (DAT_80063e68) and is visibly active in runtime transitions.

Confidence update

  • high (~0.95) that sink-side tuple shape and slot mapping remain correct.
  • high (~0.92) that the currently proven gameplay behavior-op lane is bounded to 0..9.
  • medium-high (~0.79) that the natural gate-arm event still exists in an unrecovered alternate caller/context lane rather than in the currently proven 0..9 path.

Dispatcher caller/context reassessment around 0x80026690 / 0x8002677c (2026-04-11 live pass)

This pass was dedicated to finding an active context that can naturally feed the JL-9 sink dispatcher chain, with explicit focus on behavior dispatch around 0x80026690/0x8002677c and sibling control-program runners.

Direct caller closure (behavior lane)

  1. psx_object_behavior_opcode_dispatch (0x8002677c) still has one recovered direct caller:
  • 0x80026740 inside psx_run_object_behavior_program_tick (0x80026690).
  1. That caller still enforces (opcode_word - 1) < 0x0a at 0x80026710 before calling 0x8002677c.
  2. Therefore the only proven active behavior lane currently dispatches indices 0..9, not high indices such as 54.

Secondary-dispatch topology (still valid, still not active-proven)

  1. DAT_800641ac[54] = 0x80027ecc remains true.
  2. 0x80027ecc dispatches through DAT_80063610[subop]; DAT_80063610[49] = 0x800214ac remains true.
  3. 0x800214ac remains the level-gated sink block that can route slot 0x0f into 0x800230e4.
  4. But no additional caller/xref path was recovered that proves runtime entry into 0x80027ecc or 0x800214ac from an active lane beyond table topology.

Strongest active context candidate now

The strongest active context remains the object update loop:

  • psx_object_integrate_motion_and_route_visible
  • calls psx_run_object_behavior_program_tick at 0x80012668 and 0x800131d0
  • also calls sibling psx_object_run_control_opcode at 0x80012ae0

This makes the behavior/control sibling pair the best live context family, but only the behavior half is currently connected to 0x8002677c, and that half is still bounded to < 0x0a.

54 -> 49 status update

  • keep 54 -> 49 as structurally correct table evidence.
  • retire it as an immediate active-caller explanation for now.
  • working priority should stay on recovering a second active caller/context into 0x8002677c (or alternate proven feeder into 0x800214ac).

Live Ghidra artifacts changed in this pass

  1. Created function:
  • 0x80027ecc -> psx_behavior_subopcode_dispatch (body 0x80027ecc..0x80027f34).
  1. Renamed function:
  • 0x80020f7c -> psx_control_event_apply_level_channel_preset.
  1. Added decompiler comments:
  • 0x80026710: documents < 0x0a proven caller bound.
  • 0x80027f0c: documents secondary sub-op dispatch and unresolved active reachability of entry 49.
  • 0x80020f7c: documents role as level-channel preset helper used by control-event slot handlers.

JL-9 natural-event synthesis from new clues (2026-04-11 live pass)

Scope of this pass was narrow and evidence-only: integrate the two new clues (timed failure segment and "last mission is multi-map") into a ranked natural-arm model, then apply conservative live Ghidra artifacts only where call/xref evidence is explicit.

Ranked hypothesis list (current best)

  1. Optional scripted event within countdown/transition chain (strongest)
  • slot-0x0f arm remains indirect-only (0x800640dc -> 0x800230e4) and tuple-specific (0x0a,0x04), which fits a conditional scripted branch better than a guaranteed branch.
  • progression callback lane is table-driven (0x80064210 -> 0x80027548) and therefore naturally optional/contextual.
  1. Countdown-failure branch timing miss
  • countdown tick is on active world-frame path (0x8002b9a0 -> 0x8002b738 -> 0x80020794).
  • map boundary split at 0x800208f0 (<=54 -> 0x1a, >54 -> 0x1b) gives a concrete timed bifurcation where a natural run can miss tuple timing.
  1. Map-to-map transition interaction
  • multi-map clue is code-backed via psx_map_progression_table reads in both preset and callback lanes (0x80020fa4, 0x80027560).
  • this supports "state advanced before arm tuple" as a real mechanism, but currently weaker than #1 because no single transition branch is proven to always suppress arm.
  1. Countdown-success branch itself (weakest of the four)
  • no recovered direct evidence that plain success path alone arms psx_debug_extra_channel_gate without the tuple-specific slot 0x0f branch.
  • success/failure appears to gate mode flow, while arm remains a separate control-event tuple branch.

Strongest new reason to test one path first

Most decisive next test target is the optional scripted-event path inside the countdown/transition chain.

Reason:

  • this is the only ranked path simultaneously supported by all new evidence anchors in one chain:
    • frame-timed countdown integration (0x8002b738 -> 0x80020794),
    • map-54 bifurcation (0x800208f0),
    • multi-map progression callbacks (0x80020fa4, 0x80027548/0x80027560),
    • and indirect slot-0x0f tuple arm (0x800640dc -> 0x800230e4, (0x0a,0x04)).

So the best discriminating runtime probe is now: observe whether tuple (0x0a,0x04) is skipped when the countdown branch/preset path takes the map-boundary/progression route before slot-0x0f dispatch.

Live Ghidra artifacts changed in this pass

No new function renames were applied in this pass (conservative threshold not met for additional naming).

Decompiler comments added:

  • 0x80020794: countdown tick call-chain note (level_session_loop -> world_frame_tick -> countdown_transition_tick).
  • 0x800208f0: explicit map-54 boundary bifurcation note (<=54 -> 0x1a, >54 -> 0x1b).
  • 0x80020fa4: progression-table read note with multi-map timing implication.
  • 0x80027548: callback-table-driven progression note (xref via 0x80064210).
  • 0x800230e4: indirect-only slot-table reachability reminder (0x800640dc), preserving tuple-branch specificity.