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

7 KiB

PSX JL-9 In-Level Event Investigation

This note isolates one question from the broader JL-9 work on PlayStation SLUS_002.68:

  • what natural in-level event writes psx_debug_extra_channel_gate and makes the later hidden L0SR + R1 + Circle path unlock JL-9?

Current status

The downstream half is now closed.

  • user validation proved that forcing 0x8006739d = 0x01, then entering L0SR, then pressing R1 + Circle successfully adds JL-9
  • therefore the remaining mystery is entirely on the natural gate-arm side
  • MFM4 is no longer the main mystery; it is only the strongest natural prime candidate

Current best interpretation:

  • the more direct thing being bypassed by the manual poke is the in-level event itself
  • natural MFM4 failing means MFM4 alone is not sufficient
  • the missing event is most likely an uncommon or optional scripted/control event in a small late-level family

Proven sink-side mechanics

Gate byte

  • psx_debug_extra_channel_gate = 0x8006739d
  • writer: 0x800232f0
  • reader: 0x8002fff4
  • storage is byte-wide (sb write, lbu read)
  • late check is nonzero-based, not literal-value-based

Natural gate-arm branch

Natural arm still converges on psx_set_debug_extra_channel_gate at 0x800230e4.

The exact write branch is:

  • tuple: (slot=0x0f, arg1=0x0a, arg2=0x04)
  • hidden must still be off
  • psx_level_runtime_header_state must be 3
  • only then does 0x800232f0 store 1 to 0x8006739d

Hidden trigger half

Later JL-9 unlock still requires:

  • hidden passcode branch L0SR / ?0SR
  • input code 0x1e
  • practical mapping R1 + Circle

That later path reads 0x8006739d at 0x8002fff4 and performs the extra 0x0d unlock when nonzero.

Proven dispatcher path

Sink feeder

0x800214ac..0x800215f8 is now promoted as:

  • psx_level_gate_slot_dispatch_from_action_record

Recovered structure:

  • dispatch slot byte comes from pointer at record + 0x00
  • arg1 byte comes from pointer at record + 0x08
  • arg2 byte comes from pointer at record + 0x0c
  • handler call is indirect through psx_level_gate_slot_handler_table[slot] at 0x800640a0

Important table entries:

  • psx_level_gate_slot_handler_table[0x0d] = 0x80022c6c
  • psx_level_gate_slot_handler_table[0x0e] = 0x80022ea8
  • psx_level_gate_slot_handler_table[0x0f] = 0x800230e4

Recovered slot-family behavior

Slot 0x0d

0x80022c6c is now named:

  • psx_control_event_slot0d_handler

Recovered strong branch:

  • (0x0a,0x02) => mission-complete passcode generation/display lane

Slot 0x0e

0x80022ea8 is now named:

  • psx_control_event_slot0e_handler

Recovered branches:

  • (0x0a,0x01) => mission-complete passcode generation/display lane using quad index 0x0f
  • (0x0a,0x02..0x04) => mode/timer setup branches
  • (0x0a,0x06) => selector/stage apply lane that force-applies selector 0x0f

Slot 0x0f

0x800230e4 remains:

  • psx_set_debug_extra_channel_gate

Recovered 0x0a cases:

  • 1
  • 2
  • 3
  • 4
  • 0x2e

The JL-9 natural arm branch is specifically:

  • (0x0a,0x04)

Host-level narrowing

The recovered gate family is still:

  • {54,55,56,57,58,82}

Best current host ranking:

  1. 54
  2. 55
  3. 56
  4. 57
  5. 82
  6. 58

Why 54 remains the best anchor:

  • MFM4 is the only ordinary published code that statically satisfies the prime-side conditions
  • selector 0x0f maps to map/level 54
  • DAT_80063e68[54] = 0x0f

Why this still does not close the event:

  • user-tested natural MFM4 did not produce JL-9
  • so level-family membership alone is not sufficient
  • the missing event now looks optional, late, rare, or route-specific

Upstream producer state

What still exists as topology

The older behavior/subop chain still exists in tables:

  • psx_behavior_opcode_handler_table[54] = 0x80027ecc
  • psx_behavior_subop_handler_table[49] = 0x800214ac

0x80027ecc is now named conservatively:

  • psx_behavior_subopcode_dispatch

Why it is weaker now

The only currently proven caller into psx_object_behavior_opcode_dispatch is still range-limited:

  • known caller path bounds (opcode_word - 1) < 0x0a at 0x80026710
  • that means the known active path can only reach indices 0..9
  • so 54 -> 49 is still valid topology, but it is no longer the best active explanation

Current practical reading:

  • keep 54 -> 49 alive as structure
  • deprioritize it as the leading runtime explanation until a second active caller/context is recovered

Strongest event hypothesis now

The strongest remaining hypothesis is:

  • the natural gate-arm is a sibling in the same late control-event family as mission-complete / transition handlers around 0x80022c6c..0x80023390
  • it is probably an uncommon or optional in-level scripted/control outcome
  • it is more likely tied to a rare objective-path or late-event branch than to ordinary mission completion itself

Why this is stronger than the older theories:

  • the sink-side tuple and slot family are concrete
  • mission-complete siblings are already proven in adjacent handlers
  • hard-clear / beat-the-game theory stayed weak
  • direct forced-gate tests show the real unknown is the natural writer event, not the hidden code

Ghidra changes applied in this event pass

Functions / blocks

  • 0x800204fc -> psx_show_mission_complete_congrats_text
  • 0x80020f7c -> psx_control_event_apply_level_channel_preset
  • 0x800214ac -> psx_level_gate_slot_dispatch_from_action_record
  • 0x80022c6c -> psx_control_event_slot0d_handler
  • 0x80022ea8 -> psx_control_event_slot0e_handler
  • 0x80027ecc -> psx_behavior_subopcode_dispatch

Tables / data

  • 0x80063e54 -> psx_selector_to_map_id_table
  • 0x80063e68 -> psx_map_id_to_gate_slot_table
  • 0x80063eac -> psx_map_progression_table
  • 0x80063610 -> psx_behavior_subop_handler_table
  • 0x800640a0 -> psx_level_gate_slot_handler_table
  • 0x800641ac -> psx_behavior_opcode_handler_table

Key comments added

  • 0x800214bc
  • 0x800215bc
  • 0x800215cc
  • 0x800215dc
  • 0x800215e0
  • 0x800230e4
  • 0x800232f0
  • 0x80026710
  • 0x8002685c
  • 0x80027f0c
  • 0x80034d60

Best next code targets

  1. Fully classify the control-event family around 0x80022c6c..0x80023390 as one switch/tuple cluster and align each sibling branch with concrete player-facing outcomes.
  2. Recover one actual authored producer for (slot=0x0f, arg1=0x0a, arg2=0x04) by tracing the action-record inputs feeding 0x800215cc / 0x800215e0.
  3. Find a second active caller/context into psx_object_behavior_opcode_dispatch or an alternate feeder into 0x800214ac that can justify a high-index producer path in live gameplay.

Deferred emulator experiments

Keep these queued for later:

  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: ordering test (event -> L0SR -> trigger versus L0SR -> event -> trigger)