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

230 lines
No EOL
7 KiB
Markdown

# 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`)