Research
This commit is contained in:
parent
28cbbe3470
commit
a9153546ae
56 changed files with 6731 additions and 258 deletions
112
docs/npc-action-process-class-layout.md
Normal file
112
docs/npc-action-process-class-layout.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# NPC Action Process Family Layout
|
||||
|
||||
## Purpose
|
||||
|
||||
This note captures the current safest class-lift state for the bounded seg033 NPC AI process family in live `CRUSADER.EXE`.
|
||||
|
||||
The goal is not to force a complete inheritance or process-layout model yet. The current goal is narrower:
|
||||
|
||||
- preserve the now-verified class ownership in Ghidra
|
||||
- record which methods are strong enough to live under class owners today
|
||||
- keep the remaining uncertainty explicit so later vtable and datatype work can resume without rediscovery
|
||||
|
||||
## Current Live Class Owners
|
||||
|
||||
The following class owners now exist live in `CRUSADER.EXE` under the `Remorse` namespace:
|
||||
|
||||
- `Remorse::NPCActionProcess`
|
||||
- `Remorse::StandProcess`
|
||||
- `Remorse::PaceProcess`
|
||||
- `Remorse::SurrenderProcess`
|
||||
- `Remorse::GuardProcess`
|
||||
- `Remorse::LoiterProcess`
|
||||
|
||||
This is an owner-first lift only. No full instance struct, base-process overlay, or vtable datatype has been forced yet.
|
||||
|
||||
## Landed Method Batch
|
||||
|
||||
### Base family
|
||||
|
||||
| Live address | Current name | Why it is safe |
|
||||
|---|---|---|
|
||||
| `1100:0000` | `Remorse::NPCActionProcess::Create` | Shared family create entry already named and bounded in the local process lane. |
|
||||
| `1100:1084` | `Remorse::NPCActionProcess::RunNoop` | Base run body is an intentional no-op and does not need wider semantics yet. |
|
||||
| `1100:1089` | `Remorse::NPCActionProcess::Destroy` | Shared slot-1 destroy body is grounded by live `g_*ProcessFnPtr` ownership across the NPC action family. |
|
||||
| `1100:0fe3` | `Remorse::NPCActionProcess::VtableSlot10Noop` | Shared slot-10 no-op body is structurally bounded even though final slot semantics remain open. |
|
||||
|
||||
### Derived families
|
||||
|
||||
| Live address | Current name | Why it is safe |
|
||||
|---|---|---|
|
||||
| `1100:02ed` | `Remorse::StandProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. |
|
||||
| `1100:0359` | `Remorse::StandProcess::Run` | Direct owned run body for the stand policy. |
|
||||
| `1100:1036` | `Remorse::StandProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. |
|
||||
| `1100:0383` | `Remorse::SurrenderProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. |
|
||||
| `1100:04ab` | `Remorse::SurrenderProcess::Run` | Direct owned run body for surrender behavior. |
|
||||
| `1100:0437` | `Remorse::SurrenderProcess::Destroy` | Clear family destroy path; resets the surrender vtable root, clears the local NPC flag bit, and destroys two embedded dispatch-entry children. |
|
||||
| `1100:0693` | `Remorse::PaceProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. |
|
||||
| `1100:0708` | `Remorse::PaceProcess::Run` | Direct owned run body for the pace policy. |
|
||||
| `1100:0fe8` | `Remorse::PaceProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. |
|
||||
| `1100:0984` | `Remorse::GuardProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. |
|
||||
| `1100:0a0e` | `Remorse::GuardProcess::Run` | Direct owned run body for the guard policy. |
|
||||
| `1100:0f95` | `Remorse::GuardProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. |
|
||||
| `1100:0afb` | `Remorse::LoiterProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. |
|
||||
| `1100:0bfa` | `Remorse::LoiterProcess::Run` | Direct owned run body for the loiter policy. |
|
||||
| `1100:0d3e` | `Remorse::LoiterProcess::VtableSlot10DispatchByShapeIfAlive` | Loiter-only slot-10 override is behaviorally distinct enough to separate from the shared base slot-10 no-op. |
|
||||
| `1100:0f47` | `Remorse::LoiterProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. |
|
||||
|
||||
## Shared Helper That Still Stays Free
|
||||
|
||||
`1100:0913` remains `NPC_DoRandomIdleAnimTwiceIfNotBusy` instead of being forced under one class owner.
|
||||
|
||||
Current evidence supports a helper role more strongly than a specific class-method role:
|
||||
|
||||
- the live caller map still shows only `Remorse::GuardProcess::Run` and `Remorse::LoiterProcess::Run`
|
||||
- it gates on `NPC_IsBusy`
|
||||
- it chooses between two idle-animation paths and dispatches `NPC_DoAnim` twice
|
||||
|
||||
That makes it strong enough to name and comment, but still weak for ownership under exactly one class.
|
||||
|
||||
## Ghidra Documentation Landed In-Session
|
||||
|
||||
The live database now also carries short decompiler comments on the main ownership and evidence anchors:
|
||||
|
||||
- `NPCActionProcess::Create`
|
||||
- `NPCActionProcess::Destroy`
|
||||
- `SurrenderProcess::CreateProcess`
|
||||
- `SurrenderProcess::Destroy`
|
||||
- `GuardProcess::CreateProcess`
|
||||
- `GuardProcess::Run`
|
||||
- `LoiterProcess::CreateProcess`
|
||||
- `LoiterProcess::Run`
|
||||
- `LoiterProcess::VtableSlot10DispatchByShapeIfAlive`
|
||||
- `NPC_DoRandomIdleAnimTwiceIfNotBusy`
|
||||
|
||||
Those comments preserve the current rationale without pretending that the datatype and inheritance story is already closed.
|
||||
|
||||
## Current Safest Structural Read
|
||||
|
||||
The family now reads as:
|
||||
|
||||
1. one bounded `NPCActionProcess` base with a shared create entry, shared destroy entry, and at least one shared no-op virtual slot
|
||||
2. multiple policy-specific derived families (`Stand`, `Pace`, `Surrender`, `Guard`, `Loiter`)
|
||||
3. one shared guard/loiter-side idle helper that still belongs outside the current class owners
|
||||
|
||||
That is enough for owner-first navigation in Ghidra, but not yet enough for a full ABI-clean C++ inheritance model.
|
||||
|
||||
## Things Still Open
|
||||
|
||||
The main remaining gaps are now narrower than basic object identity:
|
||||
|
||||
- what slot `10` and the adjacent slot `11` mean semantically across the broader process family
|
||||
- whether the current `NPCActionProcess::Create` should later split into a thinner base initializer plus a more explicit allocator/factory wrapper
|
||||
- whether the shared slot-1 destroy body can support a first provisional base-process overlay without destabilizing other process families
|
||||
- whether `StandProcess` and `PaceProcess` have any equally strong family-local helper methods in the same seg033 window that should move next
|
||||
|
||||
## Recommended Next Pass
|
||||
|
||||
The next pass on this family should stay conservative:
|
||||
|
||||
1. recover the process-function-table roots for the seg033 family explicitly enough to document slot order
|
||||
2. inspect local caller/callee structure around `StandProcess::Run` and `PaceProcess::Run` for family-local helpers comparable to the guard/loiter idle helper
|
||||
3. only then decide whether a provisional `/Remorse/NPCActionProcess` datatype is safe, or whether the family should remain owner-only for now
|
||||
|
|
@ -62,6 +62,14 @@ Representative maps after the extended pass:
|
|||
|
||||
So the first practical conclusion is straightforward: the exporter was the main blocker for most maps, and a constrained donor heuristic can replace a large fraction of placeholders with real graphics without touching the viewer runtime.
|
||||
|
||||
## No-Placeholder Cohort Pass
|
||||
|
||||
- A second follow-up pass replaced synthetic fallback atlas generation with cohort-aware donor resolution keyed by authored family plus raw `u5` lane before the older coarse type default is consulted.
|
||||
- The exporter still prefers direct local `DAT_800758d8` payload matches first, but when a type bucket is mixed-role or otherwise unresolved it now resolves each cohort separately instead of collapsing the whole `map:type` bucket into one donor or one placeholder.
|
||||
- Focused validation on `map 104` now rebuilds as `1002` real-art items with `0` fallback items, `1` atlas, and `136` shape definitions. Scene `sourceCounts` are now `52` `section0_dispatch_roots-art` plus `950` `section0_constructor_placements-art`.
|
||||
- The scene JSON also now preserves `mappingSource` and optional `artCohort` on each `mapSource` row, so provisional cohort and emergency donor matches stay auditable after placeholders are removed.
|
||||
- This is still not the final executable-proof binding rule. The focused cache is now placeholder-free, but many rows still resolve through `cohort-*` or `emergency-global-donor:*` provenance and should be treated as provisional PSX art recovery rather than as final family/resource closure.
|
||||
|
||||
## Remaining Unresolved Mass
|
||||
|
||||
- The cache is still not fully closed. `25,038` fallback items remain across `62` maps.
|
||||
|
|
@ -70,12 +78,71 @@ So the first practical conclusion is straightforward: the exporter was the main
|
|||
- `0x0042` remains the single largest unresolved type even after the heuristic pass.
|
||||
- `map 104` is the most obvious current outlier: it still sits at `866` fallback items versus only `136` bundle-mapped items, so the next recovery pass should treat it as the best stress case rather than relying only on the now-mostly-readable early maps.
|
||||
|
||||
Those counts are now historical rather than current for the focused `map 104` export. They remain useful as the baseline that motivated the cohort pass, but the immediate blocker has shifted from “remove placeholders” to “replace provisional donor provenance with tighter executable-backed cohort/resource rules.”
|
||||
|
||||
## Practical Interpretation
|
||||
|
||||
- The donor heuristic is good enough to prove that many placeholders were caused by missing extraction-time inheritance logic.
|
||||
- It is not strong enough to count as the final executable-backed rule for the unresolved families.
|
||||
- The remaining gap still sits where the earlier Ghidra work already pointed: somewhere between the constructor-side art/resource creation lane and the live post-spawn state/resource/frame reselection path.
|
||||
|
||||
## Palette Lock-In Rule
|
||||
|
||||
- The PSX exporter must not treat `mode 1` bundle header palette index `+0x14` as the rendered 256-color selector.
|
||||
- The current dump-grounded rule is narrower and stronger: the known-good visible family decodes against one contiguous 256-entry lookup table equivalent to live VRAM row `0xF0`, `x=0`.
|
||||
- In extractor terms, that behaves like the first `16` adjacent `16-color` CLUTs flattened into one 256-entry palette. That is the rule now encoded in `map_renderer/src/lib/psx-cache.js` for `mode 1` bundles.
|
||||
- The old bundle-header palette index is still preserved in exported scene metadata as `defaultPaletteIndex`, but it is diagnostic provenance, not the primary rendered selector.
|
||||
- `mode 2` stays on the earlier per-bundle/per-usage palette-index path. This lock-in applies specifically to the current `mode 1` family proven by the VRAM dump.
|
||||
- If a future pass proves a wider runtime CLUT-row formula, update this note and the exporter together in one change. Do not silently reintroduce `mode 1 -> bundle header palette index` as a fallback rule.
|
||||
|
||||
## Static Export Follow-Up
|
||||
|
||||
- The processed PSX catalog already carried `62` maps during this pass, so the "single map" symptom was not a cache-build enumeration failure.
|
||||
- The immediate export-side blocker was config: `psx-remorse` was excluded from static export even though the prebuilt catalog/build-manager path already supports multi-map PSX scenes.
|
||||
- The renderer config now includes `psx-remorse` in static export so full PSX exports can surface the full processed map set instead of dropping the version entirely.
|
||||
|
||||
## Live MCP Follow-Up (2026-04-12): Wall-Family Split Failure Mode
|
||||
|
||||
- Scope for this focused pass: live `SLUS_002.68` around the wall-heavy generic family band (`0x003e..0x004f`) and exporter donor-heavy bundles (`0x0008b48c`, `0x00085c40`).
|
||||
- Constructor-side bind is now explicit and consistent across this family: both `psx_object_create_simple_record` (`0x800249f4`) and `psx_object_create_compound_record` (`0x80024eec`) read the per-type active art header from `DAT_800758d8[type]` before any lane-orientation updates.
|
||||
- That means cross-family visual collapse is unlikely to be caused by a missing pre-constructor resource-bank split in this band; divergence happens after bind.
|
||||
- Post-bind state/frame path is also explicit: `psx_object_select_state_script` (`0x800260e8`) installs selector state (`obj+0x9e` and script cursor `obj+0x90`), then `psx_object_advance_state_script` (`0x80025d68`) latches live frame token `obj+0x94` from the script stream.
|
||||
- Draw submission consumes that latched token, not raw authored selector: stage-1 geometry (`0x80040d44`) and stage-2 geometry (`0x80040f78`) both query frame geometry from `obj+0x94`, and submitters read the same token in `psx_draw_main_visible_object` (`0x80041458`) / `psx_draw_special_visible_queue` (`0x80041144`).
|
||||
- The stage-1/stage-2 route split matters materially: main-visible injects authored palette token only for type bands `>=0x003e`, while special-visible does not. If exporter cohorts collapse route outcome, families can appear as repeated art with wrong face/palette behavior.
|
||||
- Practical exporter implication: the strongest missing discriminator is runtime route+latch state (effective selector/latch outcome), not a new type-level resource bank key.
|
||||
|
||||
Conservative live-artifact updates applied in Ghidra for this pass:
|
||||
|
||||
- `0x80046038`: comment clarifying `DAT_800758d8[type]` install timing and post-bind divergence expectation.
|
||||
- `0x80026100`: comment clarifying pre-latch selector install and risk of authored-`u4`-only grouping.
|
||||
- `0x80041554`: comment clarifying main-visible-only authored palette-token lane for `>=0x003e`.
|
||||
- `0x80040f88`: comment clarifying stage-2 queue still consumes `obj+0x94` but has different route semantics.
|
||||
|
||||
## Live MCP Follow-Up (2026-04-12): CLUT Override Routing Closure
|
||||
|
||||
- Scope for this focused pass: world-object draw path and submitters at `0x80041458`, `0x80041144`, `0x80044bdc`, and `0x80044e9c`, with CLUT table symbols at `0x800a9f48` and `0x800a9f66`.
|
||||
- `0x800a9f48` remains `psx_clut_table_by_resource_bank` and is both load-populated (`level_palette_upload_cluts`) and draw-consumed.
|
||||
- `0x800a9f66` remains `psx_clut_override_table_by_palette_token` and is draw-consumed by both submitters.
|
||||
- Main-visible (`0x80041458`) injects authored high-byte palette token into submit flags for type bands `>=0x003e`; special-visible (`0x80041144`) does not inject authored token and only forwards `obj_flags & 0x0002`.
|
||||
- Submitter override gate is explicit in both submitters: override is used only when `(submit_flags & 0xfffffff0) != 0`.
|
||||
- For this world-object path, token `0` behaves as "no override": with only low flag bits present, submitters fall through to default bank CLUT lookup.
|
||||
- `psx_image_table_submit_frame` uses token as a key into `psx_clut_override_table_by_palette_token[token]` when override is active.
|
||||
- `psx_sprite_resource_submit_frame` has two override lanes based on resource format field (`resource+4`):
|
||||
- format `== 2`: token keys `psx_clut_override_table_by_palette_token[token]`.
|
||||
- format `!= 2`: token is used as a row key into `psx_clut_table_by_resource_bank` (`token << 4` halfword index).
|
||||
|
||||
Exporter-facing implication from executable evidence:
|
||||
|
||||
- The current flattening bug matches a real semantic split in executable code: authored token injection is lane-dependent (main-visible only) and CLUT resolution is submitter/resource-format dependent.
|
||||
- A single flattened token->CLUT mapping in exporter code will miscolor mixed cohorts where route lane or submitter/resource format differs.
|
||||
|
||||
Conservative live-artifact updates applied in Ghidra for this pass:
|
||||
|
||||
- `0x800415b0`: comment clarifying main-visible token injection source and band split.
|
||||
- `0x800412d0`: comment clarifying special-visible non-injection behavior.
|
||||
- `0x80044e10`: comment clarifying sprite submit override gate and token-0 fallthrough.
|
||||
- `0x80044eb8`: comment clarifying image-table override keying behavior.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Trace the remaining high-volume band `0x0055..0x0063` in Ghidra with the same question used for `0x0042`: why does `DAT_800758d8` stay zero-sized while visible art still exists at runtime?
|
||||
|
|
|
|||
230
docs/psx/jl-9-in-level-event.md
Normal file
230
docs/psx/jl-9-in-level-event.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# 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`)
|
||||
2459
docs/psx/jl-9-investigation.md
Normal file
2459
docs/psx/jl-9-investigation.md
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
778
docs/psx/map-storage-model.md
Normal file
778
docs/psx/map-storage-model.md
Normal file
|
|
@ -0,0 +1,778 @@
|
|||
# PSX Map Storage Model Versus PC
|
||||
|
||||
## Scope
|
||||
|
||||
This note is a focused hypothesis document for how Crusader PSX stores map data on disk, how the executable turns that data into a renderable world, and how that model differs from the already better-understood PC build.
|
||||
|
||||
It is intentionally narrower than [map-rendering.md](map-rendering.md):
|
||||
|
||||
- this file concentrates on storage layout and platform differences
|
||||
- the rendering note concentrates on the full runtime path from load to draw
|
||||
|
||||
The claims below are evidence-backed where possible and explicitly marked as current best read where they remain inferential.
|
||||
|
||||
## Bottom Line
|
||||
|
||||
Current best read:
|
||||
|
||||
1. The PSX build does not store a level as one direct item table equivalent to the PC map arrays.
|
||||
2. A PSX level is a multi-section `LSET*.WDL` bundle that combines authored placement-like records, per-type runtime banks, palette/audio payloads, and at least one additional detached or compressed runtime-state lane.
|
||||
3. The executable turns those authored rows into live objects early, then relies on per-type state-script and runtime reselection logic to decide what finally appears on screen.
|
||||
4. The PC build appears much closer to a direct world-state model in memory: explicit map X/Y/Z arrays, entity tables, tile visibility grids, and item/shape sorting over stable world coordinates.
|
||||
5. So the main platform difference is architectural: PSX level files look more like self-contained level bundles that feed an object-construction pipeline, while PC runtime/map handling looks more like direct world-state tables plus separately loaded shape/type metadata.
|
||||
|
||||
## 2026-04-12 Live Loader Ownership Deltas
|
||||
|
||||
Live MCP pass on active `SLUS_002.68` tightened loader-owned naming/comments in the map extraction lane:
|
||||
|
||||
- `0x80067838` renamed from `section_pack_source_80067838` to `psx_level_section_pack_base`.
|
||||
- `0x800676d8` renamed from `level_clut_table_ptr` to `psx_level_clut_table_ptr`.
|
||||
- Added decompiler evidence comments at `0x800398e8`, `0x80039af0`, `0x80039250`, `0x80024c60`, and `0x8003aba8`.
|
||||
|
||||
Extractor-relevant clarified schema in this pass:
|
||||
|
||||
- `psx_level_section_pack_base` is the contiguous section-pack base established by `wdl_resource_bundle_load_by_index`; subsequent header-offset adds derive `psx_type_policy_table_ptr`, `psx_control_opcode_stream_table`, and `psx_level_clut_table_ptr`.
|
||||
- The compressed-state lane is explicit and size-stable: `psx_level_state_compressed_blob` (`0x8006b5d8`) inflates via `psx_lzss_unpack_into_level_buffer` into `psx_level_decompressed_state_buffer` (`0x8006769c`) with target size `0x3e00` before runtime-header apply and root-record dispatch.
|
||||
- `psx_lzss_pack_level_buffer` is the save-side counterpart (caller `0x80049890`) and repacks the same level-state lane, confirming this blob family is persistent runtime substrate rather than a direct authored placement stream.
|
||||
- `psx_load_type_state_banks` installs per-type runtime payload pointers into `psx_type_state_script_bank` / `psx_type_simple_component_bank` / `psx_type_companion_extents_bank`; constructors consume `psx_type_simple_component_bank[type]` at `0x80024c60` to seed object behavior program fields.
|
||||
|
||||
## 2026-04-12 Live Section-0 Descriptor Dispatch Deltas
|
||||
|
||||
Live MCP pass on active `SLUS_002.68` tightened section-0 record-family dispatch evidence for unresolved graphics-heavy types.
|
||||
|
||||
Ghidra artifacts updated in this pass:
|
||||
|
||||
- Renamed `0x800626f8` from `psx_descriptor_generic_3e_50` to `psx_descriptor_row_spawn_compound_main_visible_family`.
|
||||
- Added explicit pointer labels for neighboring unresolved-family table entries:
|
||||
- `0x8006323c` -> `psx_type_descriptor_ptr_0049`
|
||||
- `0x8006326c` -> `psx_type_descriptor_ptr_0055`
|
||||
- `0x800632a4` -> `psx_type_descriptor_ptr_0063`
|
||||
- Added decompiler comments at `0x800626f8`, `0x80063220`, `0x800256b0`, and `0x800258cc` documenting row sharing and call convergence.
|
||||
|
||||
Schema clarification closed in this pass:
|
||||
|
||||
- Descriptor row `0x800626f8` is a 3-callback family row:
|
||||
- slot0 `0x80013618` -> `psx_spawn_compound_record_advance_state_once`
|
||||
- slot1 `0x80013688` -> `psx_object_refresh_main_visible_and_cleanup`
|
||||
- slot2 `0x800254c8` -> `psx_object_release_to_free_list`
|
||||
- Both section-0 authored families route into this same row via indirect descriptor dispatch:
|
||||
- root-dispatch records (`psx_dispatch_section0_dispatch_roots`, `0x800256b0`)
|
||||
- constructor-placement records (`psx_dispatch_section0_constructor_placements`, `0x800258cc`)
|
||||
- Type `0x0042` pointer entry `0x80063220` resolves to this row, and neighboring unresolved-family entries in the requested band (`0x0049`, `0x0055..0x0063`) also resolve to the same row.
|
||||
|
||||
Practical consequence for exporter/runtime interpretation:
|
||||
|
||||
- There is currently no recovered type-unique constructor callback split between `0x0042`, `0x0049`, and `0x0055..0x0063` at section-0 descriptor-dispatch entry.
|
||||
- These families should be treated as sharing a common spawn/update/release callback chassis, with visual divergence more likely coming from per-type banks (`psx_type_art_active_header_bank`, `psx_type_state_script_bank`, policy words, and live state latches) than from descriptor-row callback identity.
|
||||
|
||||
## 2026-04-12 Live Object Spawn To Visible Frame Closure
|
||||
|
||||
Live MCP pass on active `SLUS_002.68` closed the requested spawn/selector/transition lane for map-spawned objects, centered on:
|
||||
|
||||
- `psx_object_create_simple_record` (`0x800249f4`)
|
||||
- `psx_object_create_compound_record` (`0x80024eec`)
|
||||
- `psx_spawn_compound_record_advance_state_once` (`0x80013618`)
|
||||
- `psx_spawn_simple_record_set_active_flag` (`0x8001372c`)
|
||||
- `psx_object_select_state_from_transition_table` (`0x8001bca0`)
|
||||
- `psx_object_advance_state_script` (`0x80025d68`)
|
||||
- `psx_type42_transition_selector_tick` (`0x80018578`)
|
||||
|
||||
Renames/data labels applied in this pass:
|
||||
|
||||
- `0x8003a37c`: `FUN_8003a37c` -> `psx_queue_global_draw_tint_pulse_once`
|
||||
- `0x80067544`: `DAT_80067544` -> `psx_global_draw_tint_pulse_phase`
|
||||
- `0x80067614`: `DAT_80067614` -> `psx_global_draw_tint_pulse_mode`
|
||||
|
||||
Key clarified chain (authored row -> live frame/resource decision):
|
||||
|
||||
1. Root/transition spawn dispatch builds record payloads and calls object constructors (`0x80031500`, `0x80018164`).
|
||||
2. Constructors copy authored lane/state word directly into `obj+0x1c` (`u5` lane) and seed selector/script cursor (`u4` lane) via `psx_object_select_state_script`.
|
||||
3. `psx_object_select_state_script` installs selector at `obj+0x9e` and script cursor (`obj+0x8c/0x90`), but does not write final frame token.
|
||||
4. `psx_object_select_state_from_transition_table` and `psx_type42_transition_selector_tick` can reseat selector and toggle low control bits (notably `obj+0x1c bit0x0002`) before latch.
|
||||
5. `psx_object_advance_state_script` latches live frame/state token into `obj+0x94` from the active script stream (`obj+0x90`) and updates `obj+0x96` step/countdown.
|
||||
6. Projection/draw consume `obj+0x94` directly as frame index token:
|
||||
- projection: `psx_project_object_main_visible` (`0x80040d44`), `psx_project_object_special_visible_queue` (`0x80040f78`)
|
||||
- geometry helpers: `psx_resource_frame_origin_x/y`, `psx_resource_frame_width/height`
|
||||
- submit path: `psx_draw_main_visible_object` (`0x80041458`) chooses sprite-vs-image-table by bound resource kind (`obj+0x10`), then uses `obj+0x94` for per-frame lookup.
|
||||
|
||||
State/selector semantics tightened in this pass:
|
||||
|
||||
- `obj+0x1c` broad lane/routing bits come from authored constructor copy first; transition logic mainly toggles low control bits (`0x0002`, plus local masks), not full-word replacement.
|
||||
- `obj+0x9e` is the current selector identity.
|
||||
- `obj+0x94` is the final live frame/state token used by rendering geometry/resource frame queries.
|
||||
- `obj+0x96` is step/countdown control between selector install and next frame-token latch.
|
||||
|
||||
Extractor/renderer implications:
|
||||
|
||||
- Do not treat authored selector bytes (`u4`) as final frame identity.
|
||||
- Model a two-stage decision: selector install/reseat (`obj+0x9e`) then latch (`obj+0x94`) before projection.
|
||||
- For unresolved map families (including type `0x0042`), replicate transition/reselection hooks (`psx_object_select_state_from_transition_table`, `psx_type42_transition_selector_tick`) before sampling visible frame.
|
||||
- Keep `u5` as authored lane seed, but treat runtime low-bit toggles as post-spawn modifiers, not separate authored-family evidence.
|
||||
|
||||
## Evidence For The PSX Model
|
||||
|
||||
### 1. `LSET*.WDL` is a structured level bundle, not a single map table
|
||||
|
||||
Executable-backed evidence already shows the selected `LSET*.WDL` is treated as the live level-bundle format:
|
||||
|
||||
- `lset_level_bundle_load` builds `\LSETn\Lx.WDL` paths directly
|
||||
- `wdl_resource_bundle_load_by_index` reads a fixed `0x38`-byte header whose first nine dwords act like section sizes
|
||||
- the loader does not hand one raw placement blob to the renderer; it lays out multiple runtime destinations
|
||||
|
||||
The currently identified runtime destinations are:
|
||||
|
||||
- `psx_level_root_record_stream` (`DAT_800678f4`)
|
||||
- `psx_section0_dispatch_root_records` (`DAT_80067720`)
|
||||
- `psx_section0_constructor_placement_records` (`DAT_800678f0`)
|
||||
- `psx_type_art_template_bank` (`DAT_800758d8`)
|
||||
- `psx_type_simple_component_bank` (`DAT_800758d0`)
|
||||
- `psx_type_state_script_bank` (`DAT_800758cc`)
|
||||
- `psx_type_companion_extents_bank` (`DAT_800758d4`)
|
||||
- `DAT_800675f8` per-type flags
|
||||
- `psx_level_detached_blob` (`DAT_8006767c`)
|
||||
- optional decompressed state into `psx_level_decompressed_state_buffer` (`DAT_8006769c`)
|
||||
|
||||
That alone already separates PSX from the naive "one file == one placement table" model.
|
||||
|
||||
### 2. The PSX level has at least two authored record families
|
||||
|
||||
The old viewer-side `region00/region01` names are no longer the best model. Live Ghidra work supports two authored families instead:
|
||||
|
||||
- `psx_section0_dispatch_root_records` at `DAT_80067720`
|
||||
- `psx_section0_constructor_placement_records` at `DAT_800678f0`
|
||||
|
||||
Their roles differ:
|
||||
|
||||
- dispatch roots are generic runtime-object descriptors handled by per-type dispatchers
|
||||
- constructor placements are tighter spawn inputs and already behave more like direct object-placement rows
|
||||
|
||||
This matters because it means the PSX map is not one homogeneous table of final world items. The file stores different authored row classes that the runtime interprets differently.
|
||||
|
||||
### 3. PSX level files also carry per-type runtime banks
|
||||
|
||||
`psx_load_type_state_banks` is the clearest storage-side evidence that part of the PSX level file is not map rows at all but type-local runtime support data.
|
||||
|
||||
Its current decompilation shows one serialized bank blob splitting into three parallel per-type lanes:
|
||||
|
||||
- state-script pointers into `DAT_800758cc`
|
||||
- simple-component payload pointers into `DAT_800758d0`
|
||||
- companion-extents pointers into `DAT_800758d4`
|
||||
|
||||
Separately, the template/art bank `DAT_800758d8` is assigned through its own late descriptor stream, not through the same small early-table hypothesis that was used in earlier viewer work.
|
||||
|
||||
So PSX storage combines:
|
||||
|
||||
- authored rows that say what kinds of things the level contains
|
||||
- per-type banks that say how those things behave or present at runtime
|
||||
|
||||
That is a stronger sign of a bundle-plus-runtime-data model than of a direct item-grid serialization.
|
||||
|
||||
### 4. The PSX level contains a separate decompressed state lane
|
||||
|
||||
The current `FUN_8003b00c` read is also important. It is a sliding-window decompressor that inflates one source blob into `DAT_8006769c`.
|
||||
|
||||
Current best read:
|
||||
|
||||
- the decompressed size target is `0x3e00`
|
||||
- this lane is separate from the late art bank and separate from the `psx_load_type_state_banks` blobs
|
||||
- it therefore looks like additional runtime/state substrate, not just another view of the same authored placement rows
|
||||
|
||||
That makes the PSX storage model even less like a flat map table. The file appears to contain both authored placement-like records and a second prepacked runtime-state component that is unpacked during load.
|
||||
|
||||
### 5. PSX rendering does not consume the authored selector byte directly
|
||||
|
||||
The storage model is also constrained by the runtime draw path.
|
||||
|
||||
Current verified chain:
|
||||
|
||||
- constructors write authored coordinates into `obj+0x3c/+0x40/+0x44` as `16.16` fixed-point
|
||||
- constructors preserve the original authored source-record pointer at `obj+0xa0`
|
||||
- `psx_object_select_state_script` seeds the initial live script from `DAT_800758cc`
|
||||
- `psx_object_advance_state_script` can advance that script and trigger sentinel-driven control flow
|
||||
- some families can later reseat the active script from motion/heading logic
|
||||
- final draw uses the current live state word, not just the original placement selector
|
||||
|
||||
That means part of what looks like "map appearance" on PSX is not stored directly as final art in the level rows. The level rows seed object creation, but runtime script/state logic still decides the final visible frame or variant in many cases.
|
||||
|
||||
## What The PSX `LSET*.WDL` Probably Contains
|
||||
|
||||
Current best file-level model for a retail level bundle:
|
||||
|
||||
1. Fixed top-level header.
|
||||
2. Audio/SPU-related blob near the front.
|
||||
3. One or more authored/map candidate regions.
|
||||
4. Small control/index region.
|
||||
5. Large late graphics bank region.
|
||||
6. Detached and optionally decompressed runtime-state payloads referenced by the loader.
|
||||
|
||||
For `LSET1/L0.WDL`, the currently validated carve is:
|
||||
|
||||
- `audio_or_spu_blob` at `0x34 .. 0x7010`
|
||||
- `post_audio_region_00` as a small table/directory block
|
||||
- `post_audio_region_01` and `post_audio_region_02` as the strongest map/meta candidates
|
||||
- `post_audio_region_04` as the strongest graphics bank candidate
|
||||
|
||||
For `LSET1/L1.WDL`, the same overall pattern reappears even though the raw row values do not line up one-to-one at the same offsets.
|
||||
|
||||
That cross-file repeatability is one of the main reasons the current PSX storage model is credible.
|
||||
|
||||
## How This Seems To Differ From The PC Build
|
||||
|
||||
## 1. PC evidence points to direct world-state tables
|
||||
|
||||
The PC notes show much more direct world-state storage in runtime memory:
|
||||
|
||||
- `0x7ded`: map X coordinate array
|
||||
- `0x7df1`: map Y coordinate array
|
||||
- `0x7df5`: map Z array
|
||||
- `0x7df9`: entity state array
|
||||
- `0x7e1e`: entity type table
|
||||
|
||||
The PC renderer and camera code also work directly against explicit map/tile/grid structures:
|
||||
|
||||
- a `6x5` tile-grid spatial index at `0x846a`
|
||||
- dirty/render bitmasks for tiles
|
||||
- camera/scroll globals
|
||||
- direct world-to-screen transforms over stable world coordinates
|
||||
|
||||
That looks like a more exposed runtime world model: map entries, entity tables, and visibility structures are explicit and persistent as named tables rather than being mostly reconstructed from bundle-local banks on load.
|
||||
|
||||
## 2. PC rendering appears shape/item centric, not bundle/state-script centric
|
||||
|
||||
The current PC map renderer and notes are built around:
|
||||
|
||||
- `TYPEFLAG.DAT` shape metadata
|
||||
- world-space item coordinates
|
||||
- dependency-based item sorting
|
||||
- direct world-to-screen projection with stable item footpads and shape offsets
|
||||
|
||||
The PC-side projection note is likewise straightforward:
|
||||
|
||||
$$
|
||||
screen_x = \frac{x - y}{4}
|
||||
$$
|
||||
|
||||
$$
|
||||
screen_y = \frac{x + y}{8} - z
|
||||
$$
|
||||
|
||||
That is not proof that the PC game stores levels in one trivial file format, but it does show that the practical renderer/world model is closer to direct item placement plus shape metadata than what the PSX executable is doing.
|
||||
|
||||
By contrast, PSX presentation currently depends on:
|
||||
|
||||
- authored placement rows
|
||||
- constructor family choice
|
||||
- per-type art bank
|
||||
- per-type state-script bank
|
||||
- live script advancement and possible reselection
|
||||
- palette override bytes stored in the original authored record
|
||||
- one of two world-facing render lanes
|
||||
|
||||
So the PSX runtime path is more layered even before final draw.
|
||||
|
||||
## 3. PC map state looks less self-contained inside one level asset bundle
|
||||
|
||||
Current PSX evidence suggests a single `LSET*.WDL` level bundle carries many classes of data together:
|
||||
|
||||
- placement-like authored rows
|
||||
- late sprite/graphics bank
|
||||
- type-specific runtime banks
|
||||
- audio-related blob
|
||||
- detached and decompressed runtime-state data
|
||||
|
||||
The PC notes, by comparison, point toward a more distributed asset model:
|
||||
|
||||
- map/world coordinates in explicit arrays
|
||||
- shape/type metadata from files such as `TYPEFLAG.DAT`
|
||||
- draw sorting and visibility handled over stable world entries
|
||||
|
||||
So the practical difference is not just "PlayStation uses WDL too". It is that the PSX `LSET` files appear to be denser, more self-contained level packages, while the PC game seems to expose a more table-oriented world model with auxiliary metadata coming from other resources.
|
||||
|
||||
## 4. PSX map appearance is more runtime-derived than PC map appearance
|
||||
|
||||
Current PSX evidence shows a stronger distinction between:
|
||||
|
||||
- what the level file stores directly
|
||||
- what the live runtime derives after object creation
|
||||
|
||||
Examples:
|
||||
|
||||
- the authored selector byte is only the start of state resolution
|
||||
- `DAT_800758d4` now reads as companion extents for collision/contact logic, not the missing direct art table
|
||||
- heading-based or target-based reselection can overwrite the live state used for visible resource/frame choice
|
||||
- some control-script paths can reroute camera behavior or queue deferred world changes after spawn
|
||||
|
||||
The PC evidence does not currently suggest the same degree of post-spawn reinterpretation for ordinary map storage. The DOS/Windows build still has rich runtime behavior, but the world representation looks closer to direct entries with stable positions, shape references, and tile-based visibility.
|
||||
|
||||
## What I Think The PSX Loader Is Really Doing
|
||||
|
||||
Current best reconstruction:
|
||||
|
||||
1. Open `SPEC_A.WDL` and selected `LSET*.WDL`.
|
||||
2. Read the bundle header and section-size table.
|
||||
3. Load palette/audio/front matter.
|
||||
4. Materialize authored level streams into runtime pointers such as `DAT_800678f4`, `DAT_80067720`, and `DAT_800678f0`.
|
||||
5. Load or override per-type banks into `DAT_800758cc/d0/d4`, plus the late art-template bank into `DAT_800758d8`.
|
||||
6. Load an additional detached blob and optionally decompress a runtime-state payload into `DAT_8006769c`.
|
||||
7. Dispatch the authored rows through constructor/dispatcher families to build live objects.
|
||||
8. Let runtime state scripts, motion reselection, and palette override bytes determine final visible presentation.
|
||||
|
||||
That model fits more of the verified evidence than either of the older simplified ideas:
|
||||
|
||||
- "the first small record stream is the whole map"
|
||||
- "there is one flat type-to-bundle table that directly explains all visible art"
|
||||
|
||||
## Confidence And Open Questions
|
||||
|
||||
High confidence:
|
||||
|
||||
- `LSET*.WDL` is a structured multi-section level bundle
|
||||
- the PSX level uses at least two authored record families
|
||||
- `DAT_800758d8/d0/cc/d4` are runtime per-type banks, not one homogeneous table
|
||||
- there is a separate decompressed state lane at `DAT_8006769c`
|
||||
- PSX visible art is not chosen from one flat authored selector byte alone
|
||||
- PC runtime evidence exposes direct map coordinate arrays and a tile-grid visibility system
|
||||
|
||||
Medium confidence:
|
||||
|
||||
- `post_audio_region_01` and `post_audio_region_02` are the main authored/map metadata regions
|
||||
- the PSX build is materially more self-contained per level bundle than the PC build
|
||||
- the remaining placeholder-heavy PSX families fail mainly because the final live state-to-art bridge is still incomplete, not because the whole storage model is wrong
|
||||
|
||||
Open questions:
|
||||
|
||||
- exact semantics of `post_audio_region_01` versus `post_audio_region_02`
|
||||
- exact on-disk mapping for every runtime destination loaded by the bundle loader
|
||||
- exact role of `DAT_8006769c` in ordinary map logic versus specialized level/session state
|
||||
- exact family-specific rules that map live script state to drawable resource/frame for unresolved types such as `0x0042` and `0x0049`
|
||||
- how closely any PSX authored record family corresponds to a specific PC map file structure, if at all
|
||||
|
||||
## 2026-04-12 Live Bank-Role Clarification (SLUS_002.68)
|
||||
|
||||
This pass focused only on runtime-bank and art/resource install semantics in the live MCP session.
|
||||
|
||||
- `0x8003917c` renamed to `psx_install_type_state_script_component_extents_banks`
|
||||
- `0x80045ffc` renamed to `psx_install_type_art_active_header_and_built_resource`
|
||||
- The bank globals are now explicitly separated by role:
|
||||
- `psx_type_art_active_header_bank` (`0x800758d8`) = current per-type art lane written by header installs and art install helper
|
||||
- `psx_type_art_built_resource_bank` (`0x800758c8`) = resolved/reused built resource pointer lane (kind-specific)
|
||||
- `psx_type_state_script_bank` (`0x800758cc`) = per-type state-script stream base
|
||||
- `psx_type_simple_component_bank` (`0x800758d0`) = per-type simple behavior/component payload base
|
||||
- `psx_type_companion_extents_bank` (`0x800758d4`) = per-type companion extents payload base
|
||||
- `psx_type_policy_table_ptr` (`0x800675f8`) = per-level policy bit table consumed by interaction/order/draw logic
|
||||
|
||||
Loader-to-renderer chain tightened with comments at:
|
||||
|
||||
- art install loop callsites `0x800396cc` and `0x800399b4`
|
||||
- state-bank install callsites `0x8003970c`, `0x800399f4`, and `0x80039ad0`
|
||||
- active-header table write lane `0x8003977c`
|
||||
- built resource + mirrored active lane commits `0x800460c8` and `0x800460d4`
|
||||
- policy table install/read bridge `0x800398f0` and `0x80041604`
|
||||
|
||||
Practical exporter implication: per-type art binding should treat active-header and built-resource lanes as distinct install phases that can alias after commit, while piece loading should continue to source behavior/script/extents independently from the state-bank lanes rather than assuming one fused per-type blob.
|
||||
|
||||
## 2026-04-12 Live Marker/Control Runtime-Island Clarification (SLUS_002.68)
|
||||
|
||||
This pass focused on the post-load marker/control/runtime island around `psx_selector_to_map_id_table` (`0x80063e54`), `psx_map_id_to_gate_slot_table` (`0x80063e68`), and `psx_marker_channel_runtime_block` (`0x800675ec`).
|
||||
|
||||
Direct live outcomes:
|
||||
|
||||
- Confirmed post-load sequencing in `psx_level_post_load_runtime_reset` (`0x80039ef4`):
|
||||
- restore or mode-action `8`, optional reciprocal selector/map gate check before mode-action `2`, always mode-action `4`, then `psx_marker_channel_runtime_state_snapshot`.
|
||||
- The selector/map pair is a control-gating table pair, not art binding:
|
||||
- `psx_selector_to_map_id_table` is consumed by passcode/apply and post-load checks.
|
||||
- `psx_map_id_to_gate_slot_table` is consumed by post-load and section0/slot dispatch lanes (`0x8002153c/0x800215b4`, `0x80020fbc`, `0x8002f2a0`, `0x8002f7f4`).
|
||||
- Confirmed runtime-block persistence semantics:
|
||||
- `psx_marker_channel_runtime_state_snapshot` (`0x80031878`) packs mode/step/flags with sentinels.
|
||||
- `psx_marker_channel_runtime_state_restore` (`0x80031a3c`) restores only on sentinel match, else falls back to reset helpers.
|
||||
|
||||
Live naming/comment deltas in this pass:
|
||||
|
||||
- `0x80030ed4` -> `psx_marker_channel_event_queue_reset`
|
||||
- `0x80030cf0` -> `psx_marker_channel_event_queue_accumulate`
|
||||
- `0x80030dfc` -> `psx_marker_channel_event_queue_remove_at`
|
||||
- `0x80030ebc` -> `psx_marker_channel_event_queue_get_buffer_and_count`
|
||||
- `0x80031738` -> `psx_marker_channel_get_condition_value`
|
||||
- `0x80067798` -> `psx_marker_channel_event_queue_count`
|
||||
- `0x8008f384` -> `psx_marker_channel_event_queue_entries`
|
||||
- Added durability comments at `0x8002f190`, `0x800311c4`, `0x8002f7f4`, `0x80039fe8`, `0x80031878`, `0x80031a3c`, and `0x80031738`.
|
||||
|
||||
Practical exporter/runtimeDiagnostic implication:
|
||||
|
||||
- Treat this island as runtime control-state and channel gating, not direct map-art selection.
|
||||
- If a diagnostic export wants stable post-load presentation behavior, include:
|
||||
- reciprocal selector/map-gate state,
|
||||
- runtime-block snapshot/restore validity,
|
||||
- and queued packed marker-action state (event queue count and entries) because mode-action branches can repopulate and consume that queue around post-load transitions.
|
||||
|
||||
## Detailed Extraction Plan: PSX Map To PC-Like Render Input
|
||||
|
||||
The goal here is not to prove that the PSX data really is the same as the PC format. It is to extract a PSX level into a render-oriented structure that is similar enough to the PC pipeline to reuse the existing renderer architecture where that makes sense.
|
||||
|
||||
The safest target is therefore a two-layer export:
|
||||
|
||||
- a PC-like flattened render layer for practical drawing
|
||||
- a reversible PSX evidence layer that preserves the original bundle-driven/runtime-driven structure
|
||||
|
||||
That keeps the render path usable without throwing away the information needed to correct mistakes later.
|
||||
|
||||
### Target Output Shape
|
||||
|
||||
The export should produce one per-map artifact with three conceptual parts.
|
||||
|
||||
### 1. Map header
|
||||
|
||||
Minimum fields:
|
||||
|
||||
- source file path such as `LSET1/L0.WDL`
|
||||
- bundle header summary and section boundaries
|
||||
- palette source metadata
|
||||
- unresolved-confidence flags
|
||||
- exporter version and evidence notes
|
||||
|
||||
### 2. PC-like render item list
|
||||
|
||||
This is the part meant to resemble the existing PC renderer input.
|
||||
|
||||
Each exported render item should ideally contain:
|
||||
|
||||
- stable item id
|
||||
- `type`
|
||||
- world `x/y/z`
|
||||
- projected `screen_x/screen_y` or full screen rect when known
|
||||
- bundle/frame reference or resolved art reference
|
||||
- palette index or override when known
|
||||
- render lane (`stage1` or `stage2` when known)
|
||||
- approximate draw-order key or dependency metadata
|
||||
- coarse shape/bounds for debugging and future occlusion work
|
||||
|
||||
This layer is the nearest PSX equivalent to the PC renderer's world item list.
|
||||
|
||||
### 3. PSX evidence layer
|
||||
|
||||
Each item in the flattened render list should still point back to the original PSX evidence used to build it.
|
||||
|
||||
Useful fields:
|
||||
|
||||
- source authored family: `section0_dispatch_roots` or `section0_constructor_placements`
|
||||
- source file offset or runtime pointer equivalent
|
||||
- raw row bytes or decoded raw words
|
||||
- original selector byte
|
||||
- chosen live script word if simulated
|
||||
- resolved `DAT_800758cc/d0/d4/d8` bank references
|
||||
- companion extents from `DAT_800758d4`
|
||||
- original source-record bytes used for palette override reads
|
||||
- whether the item is direct, inferred, or still provisional
|
||||
|
||||
Without this layer, the flattened format will become impossible to repair once new runtime evidence lands.
|
||||
|
||||
## Extraction Phases
|
||||
|
||||
### Phase 1: file-level bundle extraction
|
||||
|
||||
Input:
|
||||
|
||||
- selected `LSET*.WDL`
|
||||
- any required shared companion bundle such as `SPEC_A.WDL`
|
||||
|
||||
Required outputs:
|
||||
|
||||
- section header table
|
||||
- raw post-audio region boundaries
|
||||
- detached blob candidates
|
||||
- late graphics bank region
|
||||
- candidate compressed-state source blob
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- treat the file as a bundle with explicit section boundaries, not as one raw placement blob
|
||||
- record all offsets and sizes in the export header even if the semantics are still provisional
|
||||
- keep both raw offsets and normalized region-relative offsets
|
||||
|
||||
Success condition:
|
||||
|
||||
- the exporter can reproduce the known `L0.WDL` and `L1.WDL` boundary pattern consistently
|
||||
|
||||
### Phase 2: authored-row family extraction
|
||||
|
||||
Input:
|
||||
|
||||
- the regions currently believed to feed `DAT_80067720` and `DAT_800678f0`
|
||||
|
||||
Required outputs:
|
||||
|
||||
- decoded `section0_dispatch_roots`
|
||||
- decoded `section0_constructor_placements`
|
||||
- family-local row counts
|
||||
- raw row byte snapshots for each record
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- keep the two authored families separate all the way through export
|
||||
- do not collapse them into a single inferred placement table yet
|
||||
- include per-row type, authored selector, authored `x/y/z`, flags, and file offsets when available
|
||||
|
||||
Success condition:
|
||||
|
||||
- the row counts and type distributions are stable across multiple maps and match the current live viewer/export evidence
|
||||
|
||||
### Phase 3: runtime-bank extraction
|
||||
|
||||
Input:
|
||||
|
||||
- the bank blobs that feed `DAT_800758d8/d0/cc/d4`
|
||||
|
||||
Required outputs:
|
||||
|
||||
- per-type template/art descriptors from `DAT_800758d8`
|
||||
- per-type simple payloads from `DAT_800758d0`
|
||||
- per-type state-script descriptors from `DAT_800758cc`
|
||||
- per-type companion extents from `DAT_800758d4`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- preserve the exact source region for each bank
|
||||
- keep unresolved bank sub-splits explicit instead of silently normalizing them
|
||||
- export the companion extents as runtime-bounds metadata, not as final art selectors
|
||||
|
||||
Success condition:
|
||||
|
||||
- a consumer can look up all currently known bank lanes by type from the exported metadata without rereading the raw bundle
|
||||
|
||||
### Phase 4: detached and decompressed state extraction
|
||||
|
||||
Input:
|
||||
|
||||
- detached blob source for `DAT_8006767c`
|
||||
- compressed source that inflates into `DAT_8006769c`
|
||||
|
||||
Required outputs:
|
||||
|
||||
- raw detached blob dump
|
||||
- decompressed `DAT_8006769c` equivalent
|
||||
- metadata that identifies the source offset, compressed size, and output size
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- treat this as a separate lane from authored rows and per-type banks
|
||||
- do not flatten this into the item list unless a specific consumer proves how it maps to items
|
||||
- keep checksums or hashes so later passes can confirm reproducibility
|
||||
|
||||
Success condition:
|
||||
|
||||
- the exporter can regenerate the same decompressed payload for repeated runs on the same map
|
||||
|
||||
### Phase 5: constructor-faithful object seeding
|
||||
|
||||
Input:
|
||||
|
||||
- authored-row families
|
||||
- per-type banks
|
||||
|
||||
Required outputs:
|
||||
|
||||
- one provisional live object per constructor-backed authored row
|
||||
- separate dispatch-root-backed provisional objects when the type path supports them
|
||||
|
||||
Minimum object fields:
|
||||
|
||||
- `type`
|
||||
- authored `x/y/z`
|
||||
- `world_x/world_y/world_z` in PSX fixed-point form when known
|
||||
- original selector byte
|
||||
- initial live script reference
|
||||
- resource/template reference
|
||||
- source authored family and source row offset
|
||||
- palette override source bytes
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- keep both raw authored coordinates and normalized runtime coordinates
|
||||
- preserve the original source-record pointer semantics in metadata even though there is no literal RAM pointer in offline export
|
||||
- mark whether an object came from direct constructor logic or from a more generic dispatch-root path
|
||||
|
||||
Success condition:
|
||||
|
||||
- the exported object model is rich enough to simulate initial state selection and later art/frame resolution without re-reading raw rows
|
||||
|
||||
### Phase 6: initial live-state simulation
|
||||
|
||||
Input:
|
||||
|
||||
- seeded objects
|
||||
- `DAT_800758cc` state-script bank
|
||||
|
||||
Required outputs:
|
||||
|
||||
- initial live script word per object
|
||||
- script-sentinel interpretation trace where available
|
||||
- any immediate reselection or control-side effects that happen before the object becomes visible
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- this phase should be deterministic and conservative
|
||||
- only apply behavior that is currently proven from executable evidence
|
||||
- keep a trace record that shows whether the chosen state came directly from the authored selector or from a later reselection rule
|
||||
|
||||
Success condition:
|
||||
|
||||
- for solved families, the chosen script word matches current executable-backed expectations
|
||||
|
||||
### Phase 7: art and palette resolution
|
||||
|
||||
Input:
|
||||
|
||||
- seeded live objects
|
||||
- resolved live script words
|
||||
- template bank, source-record bytes, and known palette override rules
|
||||
|
||||
Required outputs:
|
||||
|
||||
- chosen resource or bundle reference
|
||||
- chosen frame index when known
|
||||
- chosen palette index or override byte when known
|
||||
- confidence label: `verified`, `family-rule`, or `provisional`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- use narrow family-specific rules only where current evidence supports them
|
||||
- do not use `DAT_800758d4` as a fallback art selector
|
||||
- preserve both default palette assumptions and authored override bytes so color work stays reversible
|
||||
|
||||
Success condition:
|
||||
|
||||
- the export produces a partially solved but clearly labeled art assignment rather than a misleadingly "complete" one built on weak heuristics
|
||||
|
||||
### Phase 8: projection into a PC-like item list
|
||||
|
||||
Input:
|
||||
|
||||
- resolved or provisional live objects
|
||||
|
||||
Required outputs:
|
||||
|
||||
- flattened render items with PC-like world coordinates and draw metadata
|
||||
|
||||
Projection rules already supported by current evidence:
|
||||
|
||||
- `screen_x = y - x`
|
||||
- `screen_y = 2*z - (x + y)/2`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- keep stage-1 and stage-2 lane identity in metadata even if both are drawn into one final frame in the first renderer pass
|
||||
- include screen rectangles when known because the runtime already caches them at `obj+0x20..+0x2e`
|
||||
- include debug fields that show both raw PSX coordinates and PC-like renderer coordinates
|
||||
|
||||
Success condition:
|
||||
|
||||
- the flattened list can be fed through the existing renderer with only PSX-specific sort/projection differences layered on top
|
||||
|
||||
### Phase 9: render-order approximation
|
||||
|
||||
Input:
|
||||
|
||||
- flattened item list
|
||||
- render lane metadata
|
||||
- available bounds and screen rectangles
|
||||
|
||||
Required outputs:
|
||||
|
||||
- stable per-item sort keys or dependency edges
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- do not assume simple `y` sorting is enough
|
||||
- use whatever executable-backed visible-list ordering facts are currently known
|
||||
- keep dependency metadata explicit so the renderer can later upgrade from scalar sort keys to graph ordering if needed
|
||||
|
||||
Success condition:
|
||||
|
||||
- the rendered scene is stable and debuggable even when some art remains provisional
|
||||
|
||||
### Phase 10: validation against the PC-like renderer contract
|
||||
|
||||
Input:
|
||||
|
||||
- final flattened item list
|
||||
- evidence layer and raw extraction metadata
|
||||
|
||||
Required outputs:
|
||||
|
||||
- scene JSON or equivalent artifact ready for the renderer
|
||||
- validation report listing which items are verified, inferred, or unresolved
|
||||
|
||||
Minimum validation checks:
|
||||
|
||||
- row counts are reproducible across runs
|
||||
- extracted banks and decompressed blobs are reproducible across runs
|
||||
- constructor-placement `z` values produce the expected multi-level output rather than a flat plane
|
||||
- known solved art families map to the same bundle/frame choices as the current viewer evidence
|
||||
- stage-1 versus stage-2 objects remain distinguishable in metadata
|
||||
|
||||
## Recommended Export Format
|
||||
|
||||
The closest useful analogue to the PC renderer input is a scene format with:
|
||||
|
||||
- `items`: flattened render items
|
||||
- `mapSource`: original PSX authored-row metadata
|
||||
- `stateLayers`: decoded per-type runtime banks
|
||||
- `bundleInfo`: resolved art/palette references
|
||||
- `evidence`: loader boundaries, decompression metadata, confidence flags, and unresolved notes
|
||||
|
||||
If the renderer needs something even closer to the PC item list, derive it as a second view rather than making it the only exported representation.
|
||||
|
||||
Good compromise:
|
||||
|
||||
- primary export: reversible PSX scene JSON
|
||||
- secondary export: flattened PC-like render list derived from that scene JSON
|
||||
|
||||
## What "Similar To PC" Should Mean In Practice
|
||||
|
||||
The output should be similar to the PC format in renderer ergonomics, not in pretending the source data matches structurally.
|
||||
|
||||
That means:
|
||||
|
||||
- one row per drawable or potentially drawable object
|
||||
- stable world coordinates
|
||||
- stable art/frame/palette fields when known
|
||||
- enough bounds and sort metadata for a normal scene renderer
|
||||
- enough source metadata to trace every rendered item back to its PSX origin
|
||||
|
||||
It should not mean:
|
||||
|
||||
- forcing all PSX authored families into one fake item table
|
||||
- discarding stage/lane distinctions
|
||||
- flattening away state-script provenance
|
||||
- treating unresolved art heuristics as solved data
|
||||
|
||||
## Short Practical Sequence
|
||||
|
||||
If this had to be implemented in the current codebase as the next extraction push, the order should be:
|
||||
|
||||
1. Stabilize section-boundary extraction for all `LSET` maps.
|
||||
2. Export `section0_dispatch_roots` and `section0_constructor_placements` as separate row families with raw bytes.
|
||||
3. Export `DAT_800758d8/d0/cc/d4` into `stateLayers` with exact source offsets.
|
||||
4. Export the detached and decompressed state lanes separately.
|
||||
5. Build a constructor-faithful provisional object list.
|
||||
6. Simulate only the currently verified state-selection rules.
|
||||
7. Resolve only the currently verified art and palette rules.
|
||||
8. Flatten into a PC-like render list while keeping evidence links back to the PSX source.
|
||||
9. Render with explicit unresolved markers instead of aggressive heuristics.
|
||||
10. Iterate family by family until the provisional cases shrink.
|
||||
|
||||
## Practical Conclusion
|
||||
|
||||
If the goal is to reproduce PSX maps faithfully, the safest current approach is:
|
||||
|
||||
- treat `LSET*.WDL` as a bundle, not a flat placement file
|
||||
- keep authored record families separate
|
||||
- keep type banks separate from placement data
|
||||
- keep the decompressed state lane separate from both
|
||||
- model the runtime as object construction followed by script/state-driven presentation
|
||||
- compare against PC at the level of architecture, not by forcing PSX rows into the PC item-table mental model
|
||||
|
||||
The strongest current difference is therefore architectural rather than cosmetic:
|
||||
|
||||
- PC looks like a more direct world/item/tile representation with auxiliary type metadata
|
||||
- PSX looks like a bundled authored-record plus runtime-bank package that must be interpreted through constructors and state-script logic before the final map image exists
|
||||
|
|
@ -28,22 +28,53 @@
|
|||
- One renderer-side mismatch is now closed: PSX sprites use authored `item.screen` rectangles, and the bounding/highlight overlay path now uses those same authored rectangles instead of recomputing a DOS-style wireframe from provisional `world` coordinates.
|
||||
- The executable now closes the last projection stage: authored object coordinates land in object fields `+0x3c/+0x40/+0x44` as `16.16` fixed-point values, and `FUN_80040d44` / `FUN_80040f78` project them with `screen_x = y - x` and `screen_y = 2*z - (x + y)/2` before writing the final screen rectangle at `+0x20..+0x2e`.
|
||||
- Palette handling is partially grounded by runtime VRAM evidence, but the per-placement override rule is still missing.
|
||||
- Palette override provenance is now tighter than that older summary: `psx_draw_main_visible_object` only applies authored override bytes for visible type bands `0x003e..0x00ab` and `>= 0x00ac`, and it reads them from different source-record words (`source+0x06` high byte for the earlier band, `source+0x0c` high byte for the later band). Types below `0x003e` do not take that override path in the main visible helper.
|
||||
- The render-lane split matters for color too, not just visibility. `psx_draw_special_visible_queue` reuses the same frame/resource submitters as the main visible pass but does not apply the authored palette override byte at all, so any exporter rule that assumes one palette path for all world-facing objects is now too broad.
|
||||
- The submitters themselves also separate the palette story by resource class. `psx_sprite_resource_submit_frame` and `psx_image_table_submit_frame` both consume the same high-byte override token, but they translate it through different CLUT lookup tables depending on the bound resource kind, so the remaining viewer bug is not just `which palette index` but `which palette table for which resource class and render lane`.
|
||||
- The constructor/resource side is narrower now too. Both `psx_object_create_simple_record` and `psx_object_create_compound_record` resolve the drawable resource at spawn time from `DAT_800758d8` before the object enters the live update loop, so the remaining repeated-wall problem is no longer best framed as a missing late resource allocator. The open bridge is the family-specific post-spawn state/frame route plus the correct lane-aware palette policy.
|
||||
- The scene/cache naming now uses executable-backed family names (`section0_dispatch_roots`, `section0_constructor_placements`) with the old `region00/region01` labels kept only as legacy aliases.
|
||||
- The offline `FUN_8003b00c` path now exists in the renderer-local exporter and serializes one candidate on-disk compressed source plus the decoded `0x3e00` state buffer into the cache for each map.
|
||||
- The type-to-art pass is still open. The exporter now scans parsed per-type template-bank payloads for bundle references, and it no longer promotes the disproven scan-order bundle fallback into visible map art. Unverified types stay on placeholders until the executable state/type path yields a real art binding.
|
||||
- That loader-shaped bank selection is now already paying off in the live cache: map `9` moved from `0` resolved bundle-mapped items to `111` after the template pass switched to the embedded late-section parse, even though unresolved root-dispatch families such as `0x0042` and `0x0049` still need the downstream state/variant path before they can stop using placeholders.
|
||||
- Current status: the PSX viewer output is still unreadable as a practical map. The exported placements and projection are good enough to sketch room massing, but most visible section-0 families still lack the executable's final state/variant-driven art binding and therefore remain on placeholders.
|
||||
- Current status: the focused PSX exporter is no longer placeholder-driven on `map 104`. Placements and projection remain structurally correct, and the current cache now resolves all `1002` records to actual PSX bundle art, but many of those bindings are still provisional donor selections rather than final executable-proved family/resource closure.
|
||||
- A focused art-binding recovery pass is now landed in the cache builder too; see `docs/psx/art-binding-recovery.md` for the measured recovery note. The exporter now treats many zero-block `DAT_800758d8` constructor-placement types as inherited-art candidates instead of dropping directly to placeholders, first via same-map `DAT_800758cc` script-signature donors and then via a constrained nearest-donor fallback inside the current `0x003e..0x0064` constructor-placement family band.
|
||||
- That heuristic materially improved the built cache even though it is still provisional rather than executable-proved. The latest rebuild moved the scene set from `58,262` fallback items / `1,714` bundle-mapped items down to `25,038` fallback items / `34,938` bundle-mapped items. Representative maps such as `0`, `9`, and `43` are now mostly real-art scenes, while `map 104` remains the clear outlier with `866` fallback items versus only `136` bundle-mapped items.
|
||||
- The newest cache slice shows `map 104` is failing in a more specific way than “one unresolved type keeps using one bad wall.” The repeated `0x0042` wall cluster currently spans both `section0_constructor_placements` (`u5=0x0020`) and `section0_dispatch_roots` (`u5=0x0030`, plus smaller `0x0022` outliers), yet all of those records still collapse onto donor map `85` type `0x0040` bundle `0x0009d304` with palette `0`. That means the current donor recovery is over-merging at least two authored/runtime roles before the executable-backed family/resource split is proven.
|
||||
- The viewer/exporter now has a stronger follow-up for that exact failure mode. Provisional donor matches in the unresolved generic family still do not apply as one coarse mixed-role `map:type` default, but the cache builder now resolves those rows per authored-family plus raw-`u5` cohort and emits actual PSX art instead of synthetic fallback atlases. Focused `map 104` validation now exports `52` `section0_dispatch_roots-art` items plus `950` `section0_constructor_placements-art` items with `0` fallback records, `1` atlas, and `136` shape definitions.
|
||||
- That no-placeholder pass is intentionally provenance-heavy instead of pretending closure. Scene `mapSource` rows now preserve `mappingSource` and optional `artCohort`, so wrong-art cases can be audited as `cohort-*` or `emergency-global-donor:*` matches without reintroducing placeholders into the live renderer.
|
||||
- The latest six-track Ghidra pass narrows that unresolved split further. `0x0042` does not have a unique descriptor entry; it shares the same generic descriptor object as the wider `0x003e..0x0050` band. For constructor-placement `0x0042`, the strongest current path is now explicit: compound spawn wrapper -> `psx_object_create_compound_record` -> `psx_object_advance_state_script` -> main-visible refresh/project -> stage-1 draw. For root-dispatch `0x0042`, the dominant `u5=0x0030` band and smaller `u5=0x0022` outliers currently still look like the same broad main-visible runtime role, with `0x0002` acting more like an orientation/extents variant than a separate lane.
|
||||
- The same pass also closes the submitter side enough to constrain the next art-binding experiment. In both world-facing lanes, draw-time submitter choice is based on the bound resource header kind: `kind == 5` uses `psx_image_table_submit_frame`, otherwise the executable uses `psx_sprite_resource_submit_frame`. The HUD/overlay lane is now explicitly separated from that rule for viewer purposes: it can route by overlay-slot policy instead of normal world-object kind checks, and `psx_draw_clock_digits_overlay` is a fixed image-table user rather than evidence for map-object family binding.
|
||||
- The newest six-track pass tightens the route-state side as well. Constructor and root-dispatch records both hand their authored lane/flags word into `obj+0x1c`; current best read is `0x0020 = broad world-visible route gate`, `0x0002 = orientation/extents-axis behavior`, and `0x0400 = stage-2 special-visible selector`. That means the current `u5=0x0030` versus `0x0022` split is still better treated as same-family main-visible routing with different orientation behavior, while the actual stage-1 versus stage-2 split should now be sampled through `obj+0x1c` bit `0x0400` instead of inferred from raw `u5` alone.
|
||||
- The selector side is narrower now too. `psx_object_select_state_from_transition_table` (`DAT_80063b4c` row indexed by `type - 0x1e`) is now a concrete per-type selector source ahead of `psx_object_select_state_script`, and `psx_object_update_interaction_transition` shows one forced-selector path that drives helper objects to selector `3` unless they are already in `1` or `3`. So the next `0x0042` pass should correlate `type 0x0042` transition-table output with the later `obj+0x94` live script word instead of treating selector `3/4` only as a generic post-spawn mystery.
|
||||
- The newest follow-up narrows `0x0042` selector recovery again. Type `0x0042` now has its own transition helper, `psx_type42_transition_selector_tick`, and that helper can dispatch low turning selectors `3/4` before the `+0x94`-style runtime latch copy. The current `DAT_80063b4c` row for type `0x0042` mostly yields higher script selectors, so selector `3/4` cases now look more like pre-latch turn/reseat outcomes than direct row literals.
|
||||
- The exact descriptor slot is now closed too: `psx_type_descriptor_table[0x0042]` at `0x80063220` points to the shared row `0x800626f8`, whose create/update/release callbacks are `psx_spawn_compound_record_advance_state_once`, `psx_object_refresh_main_visible_and_cleanup`, and `psx_object_release_to_free_list`. Both section-0 constructor placements and root-dispatch rows still enter type `0x0042` through that same initial descriptor family.
|
||||
- The per-type art/cache lane is narrower too. `DAT_800758d8` and `DAT_800758c8` no longer read as a simple immutable descriptor table plus separate cache; the active helper first seeds `DAT_800758d8[type]` with the incoming art header, then writes the built resource pointer back to both slots. So the remaining `0x0042` question is still runtime family/resource selection, but the notes should treat the pair as active per-type art state rather than as a permanently raw-versus-cached split.
|
||||
- The newest follow-up tightens those two lanes again. The live names now reflect the stronger art/cache read: `DAT_800758d8 = psx_type_art_active_header_bank` and `DAT_800758c8 = psx_type_art_built_resource_bank`. On the selector side, `psx_type42_transition_selector_tick` now has one more concrete gate: it first checks `psx_object_is_within_view_margin`, then can remap a bucket from `psx_object_compute_heading_selector_to_focus` and dispatch selector `3/4` before the later latch copy. So the next runtime sample should include both the pre-latch dispatch state and the later `obj+0x94`-style latch, not just one or the other.
|
||||
- The route-bit writer question also narrowed, but is not fully closed yet. The recovered anonymous islands now prove that `0x0400` is written in the wider nested runtime state machine (`psx_object_state_machine_dispatch_tick`) and that `psx_object_handle_control_pair_0a` mutates related policy state plus object-local bit `0x0200`, but this batch still did not pin a direct object-local `obj+0x1c |= 0x0400` writer for `0x0042`. That keeps the next concrete sample focused on runtime state plus object-local flags rather than assuming the stage-2 bit is only an authored `u5` literal.
|
||||
- Step 2 is now landed on the viewer side as well. `map_renderer/src/lib/psx-cache.js` now exports a per-item `runtimeDiagnostic` payload in scene version `psx-runtime-record-probe-v10`, carrying the split the last Ghidra passes converged on: object-local route flags, selector seed/pre-latch hints, exporter-side latched-state candidate, nested-runtime placeholders, resource-kind hints, and a placeholder slot for the live `DAT_800675f8` policy word. The immediate next Ghidra work should now fill those channels for representative `map 104` `0x0042` items rather than redefining the channel model again.
|
||||
- That first concrete `runtimeDiagnostic` pass is now done too. `item:25`/`35` (root `0x0022`), `item:30`/`31` (root `0x0030`), `item:85`/`86` (constructor `0x0030`), and control `item:53` now form the fixed `map 104` sample pack for future `0x0042` checks instead of broad family-level prose.
|
||||
- The family bridge is explicit now: `psx_dispatch_section0_dispatch_roots` and `psx_dispatch_section0_constructor_placements` still converge through the same shared `0x0042` descriptor row into `psx_spawn_compound_record_advance_state_once`, then the shared constructor/state path. Family identity still matters as an exporter fence, but it is not a different descriptor family.
|
||||
- `runtimeDiagnostic.objectLocalRouteFlags` is better grounded too. Constructors directly copy the authored lane word into `obj+0x1c`, so the exported `initialWord` values are real initial authored state. For the current sample pack, `0x0022` is authored `{ 0x0020, 0x0002 }`, while `0x0030` is authored `{ 0x0020, 0x0010 }`.
|
||||
- `runtimeDiagnostic.selector` also stays justified as a separate channel from the later latch. Spawn-side selector seeding now has a named bridge through `psx_transition_spawn_and_seed_selector_from_record`, but `0x0042` still uses `psx_type42_transition_selector_tick` to emit selector `3/4` before the later latch copy.
|
||||
- `runtimeDiagnostic.nestedRuntimeState` remains necessary too. The strongest recovered `0x0400` stage-selection write is still nested-state-side, and this pass still did not pin a direct object-local `obj+0x1c |= 0x0400` writer for `0x0042`.
|
||||
- `runtimeDiagnostic.typePolicy` is narrower now as well. `DAT_800675f8` is now explicitly `psx_type_policy_table_ptr`, installed during level load and then read by type-indexed consumers, so the eventual numeric policy word should be treated as type-global within the loaded level rather than as a per-lane value.
|
||||
- The next follow-up widened the same concrete sample pack in three practical directions: resource identity, frame-state flow, and explicit routing branches. The strongest current read is still that `map 104` `0x0042` is not yet split by a different descriptor family or an obvious different bind path; the remaining unresolved split is now mostly `bound resource identity` plus `live frame/state token` plus `object-local 0x0400 at route time`.
|
||||
- The fixed `map 104` sample pack should therefore stay frozen for the next pass: root `0x0022` = `item:25/35`, root `0x0030` = `item:30/31`, constructor `0x0030` = `item:85/86`, control `0x0066` = `item:53`.
|
||||
- The storage-side section family is tighter now too. The latest pass promotes `DAT_80067938` to `psx_ctor_placement_section_ptr`: a constructor-placement section installed during `wdl_resource_bundle_load_by_index` whose rows still read as six-halfword `0x0c`-stride records `[type, x, y, z, selector, u5]`. That strengthens the current read that map-object construction is fed from explicit section-pack row families rather than from one opaque monolith.
|
||||
- Adjacent section-pack pointers are narrower as well. `DAT_80067838` is now at least a real level section base in the same install family rather than an unexplained scalar, `DAT_80067840` now reads as a `resource_bundle_ptr_table` with control/opcode-stream consumers, and `DAT_800676d8` now reads as `level_clut_table`, which keeps it on the palette/CLUT side instead of the missing placement/art side.
|
||||
- The decompressed lane is tighter too. `DAT_8006769c` is now confirmed again as `psx_level_decompressed_state_buffer`, written during `wdl_resource_bundle_load_by_index`, passed to `psx_lzss_unpack_into_level_buffer`, and then reused by save/runtime snapshot code. Current best read is that it primarily seeds runtime-bank installs and broader session/runtime state rather than acting as one hidden flat item table.
|
||||
- The `0x80063e54/0x80063e68/0x800675ec` control island is now promoted from background curiosity to an active blocker family. `0x800675ec` is now `psx_marker_channel_runtime_block`, with the strongest current field map at `+0x34` mode byte, `+0x6c` mode-step byte, and `+0x88/+0x8c` short fields that are still the most plausible presentation-adjacent members. That family still needs an explicit struct/table dump before it can be ruled in or out for map-facing `0x0042` divergence.
|
||||
- Loader ownership is tighter too: `psx_load_type_state_banks` now reads as the `DAT_800758cc/d0/d4` installer only, `psx_stream_install_type_runtime_banks` is the packed-stream helper that can install all four banks including `DAT_800758d8`, and the `DAT_80067794` header block now reads as save/transition state through `psx_snapshot_level_runtime_header_block` / `psx_apply_level_runtime_header_block`, not as the missing `0x0042` art-binding source.
|
||||
- The old fallback art binding is now positively disproven for map rendering, not just "still unverified": in the live cache, early `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles (for example map `0` offsets `0x000B2970` and `0x000D84F4`), which confirms the section-0 dispatch rows are generic runtime-object descriptors whose visible art still depends on downstream per-type state/variant selection.
|
||||
- The executable-side type path is now clearer and named in the live PSX Ghidra database. `psx_object_create_simple_record` and `psx_object_create_compound_record` both index the same per-type banks rooted at `DAT_800758d8/d0/cc/d4`; `psx_object_select_state_script` selects an active state script from `DAT_800758cc`, `psx_object_advance_state_script` at `0x80025d68` interprets sentinel-driven script records, `psx_object_lookup_variant_entry` resolves a companion entry from `DAT_800758d4`, and `psx_reset_type_runtime_banks_from` at `0x80025ce8` is the nearby bank-reset helper that had been misnamed earlier. So the missing map-render rule is not one flat `type -> bundle` table but a multi-stage runtime selection path.
|
||||
- The visible render pass is less opaque now too. `FUN_80041378` draws in three stages: the sorted visible-object list through `FUN_80041458`, a second special-visible list through `FUN_80041144`, and then HUD/overlay/icon primitives through `FUN_800416cc`. That means the remaining map-viewer gap is still mainly in world-object and special-object families, not in the HUD pass.
|
||||
- That draw tail is now named more tightly in Ghidra too: `FUN_800416cc` is now `psx_draw_hud_overlay_pass`, and its clock/timer child `FUN_8004214c` is now `psx_draw_clock_digits_overlay`.
|
||||
- The next decompilation target is narrower now too: `FUN_8002906c` is the highest-value follow-up because it is a verified post-construction state reselection path that can overwrite the live script choice from `FUN_8003bc1c(obj) >> 2 & 0xf`, which is exactly the missing bridge between exported placement selectors and the art-facing `DAT_800758d4` variant lookup.
|
||||
- The stage-2 path is now strong enough to affect renderer planning directly. `FUN_80040f78` is the queue-builder for the `FUN_80041144` pass: it projects an object just like the main `FUN_80040d44` path but appends it to `DAT_80078b70` / `DAT_80067472` instead of the main `DAT_8006ad5c` visible list. So a renderer that only models the stage-1 visible list will still miss a real world-facing object lane.
|
||||
- The broader lifecycle is readable now too. `psx_level_session_loop` is the outer level-session loop; `wdl_resource_bundle_load_by_index` performs the actual `SPEC_A.WDL` + selected `LSET*.WDL` load and root-record dispatch; `psx_world_frame_tick` is the normal per-frame world loop; and `FUN_80041378` is the top-level draw submission.
|
||||
- The authored record passes now line up with the viewer model closely enough to use as the current executable ground truth: `psx_dispatch_section0_dispatch_roots` dispatches the `DAT_80067720` `0x18`-stride family plus the extra fixed-size entries near `DAT_80067658`, while `psx_dispatch_section0_constructor_placements` dispatches the `DAT_800678f0` `0x0c`-stride family. Those are the closest executable matches for the current `section0_dispatch_roots` and `section0_constructor_placements` viewer families.
|
||||
- The live-object passes are separated too: `psx_run_live_object_type_updates` runs the per-type update callback over the linked live object list at `DAT_800675ac`, `psx_run_live_object_behavior_callbacks` runs the later per-object behavior callback stored on each object, and `FUN_80029de0` is the broad world/player motion integrator that sits between behavior updates and draw submission.
|
||||
- The cull/draw bridge is now explicit too: `FUN_800423b0` gates the two authored record-family dispatch passes, `FUN_80042424` gates already-instantiated live objects, and `FUN_80041458` draws from the final authored screen rectangle while sourcing palette overrides directly from the original record pointer at `obj+0xa0`.
|
||||
- The live-object passes are separated too: `psx_run_live_object_type_updates` runs the per-type update callback over the linked live object list at `DAT_800675ac`, `psx_run_live_object_behavior_callbacks` runs the later per-object behavior callback stored on each object, and `psx_update_motion_and_nearby_interactions` is the broad world/player motion integrator that sits between behavior updates and draw submission.
|
||||
- The cull/draw bridge is now explicit too: `psx_authored_record_in_view_bounds` gates the two authored record-family dispatch passes, `psx_world_point_in_view_bounds` gates already-instantiated live objects, and `FUN_80041458` draws from the final authored screen rectangle while sourcing palette overrides directly from the original record pointer at `obj+0xa0`.
|
||||
- Palette override provenance is tighter too: object field `+0xa0` is the original authored source-record pointer written by both constructors, so the current override path in `FUN_80041458` is reading authored record bytes directly rather than a hidden runtime side table.
|
||||
- The cache builder now uses that evidence too: shared PSX reference art is keyed by `map:type:palette`, and scene export applies the authored palette byte when the source record exposes the proven override lane instead of always baking the bundle default palette.
|
||||
- One narrow renderer-side consequence is now verified in output, not just in notes: the cache builder now applies the executable-backed `0x0050` selector map (`0..3 -> frame 0..3`) as a temporary fallback, and retail map `9` now exports `type=80 state_selector=1 chosen_frame=1` instead of forcing frame `0`.
|
||||
|
|
@ -56,19 +87,23 @@
|
|||
- The unresolved type path is more clearly dynamic than before: `psx_object_lookup_variant_entry` is called from both constructors and from `psx_object_advance_state_script`, so a root-dispatch family can change its visible companion bytes after script jumps instead of fixing them only at spawn time.
|
||||
- The built scene cache now tightens the remaining `0x0042` blocker too. The exported `state_selector` label is confirmed to be raw word `u4`, not a derived guess, while `lane` is raw word `u5`. A full cache scan found `3944` `type=0x0042` placeholders across `61` maps and showed selectors `0..4`, with real `3`/`4` cases on `map-4`, `map-5`, `map-8`, `map-45`, `map-69`, and `map-85`.
|
||||
- That same cache scan shows lane and selector are not interchangeable. `0x0042` still clusters mostly on lanes `0x0020` and `0x0022`, but there are also `lane=0x0030` exports with `state_selector=0` (for example on `map-108`). So the next executable-backed pass should treat `u4` as the confirmed selector input and `u5` as a separate lane/class byte while tracing the post-advance `DAT_800758d4` variant lookup.
|
||||
- The next narrowing pass is now clearer too: the art-facing variant index is not the raw placement selector byte directly. `psx_object_select_state_script` stores the authored selector in `obj+0x9e`, but `psx_object_lookup_variant_entry` indexes `DAT_800758d4` with `obj+0x94`, the current script word. Two runtime paths (`FUN_80028c94` and `FUN_8002906c`) can reseat the active script from `FUN_8003bc1c(obj) >> 2 & 0xf`, so at least some live `0x0042` selectors `3` and `4` come from heading-based runtime reselection rather than from the original record alone.
|
||||
- The next narrowing pass is now clearer too: the art-facing variant index is not the raw placement selector byte directly. `psx_object_select_state_script` stores the authored selector in `obj+0x9e`, but `psx_object_lookup_variant_entry` indexes `DAT_800758d4` with `obj+0x94`, the current script word. Two runtime paths (`psx_object_reselect_state_from_target_vector` and `psx_type4_reselect_motion_state`) can reseat the active script from `psx_object_quantize_motion_heading16(obj) >> 2 & 0xf`, so at least some live `0x0042` selectors `3` and `4` come from heading-based runtime reselection rather than from the original record alone.
|
||||
- The newly traced consumer side narrows that further. `psx_object_advance_state_script` sign-extends the `DAT_800758d4` lookup result into `obj+0x30/+0x34/+0x38`, and the verified downstream consumers of those fields are collision/contact helpers (`psx_object_test_overlap_3d`, `psx_object_update_contact_block_flags`, and the target-bounds reads inside the reselection helpers), not the visible draw path. So the practical exporter change is to serialize `DAT_800758d4` as signed companion-extents metadata and stop treating it as the leading candidate for the missing final art table.
|
||||
- That exporter step is now landed in the viewer-side cache path too. `buildTypeCompanionExtentsMaps` decodes the exported `DAT_800758d4` layer as a `u32 count + packed 3-byte signed extents` table, scene `stateLayers` now preserve those decoded extents per type/state, and each exported PSX scene item and `mapSource` row now carries the resolved `companionExtents` tuple for its chosen live state when one is available.
|
||||
- That moves the remaining art problem to a more specific place: unresolved families still need a family-specific rule that explains how the live script word at `obj+0x94` interacts with the drawable resource at `obj+0x10` after post-spawn reselection. The next useful trace should therefore stay close to those family-specific presentation callers instead of spending more time on generic `DAT_800758d4` consumers.
|
||||
- The latest dispatch-table pass narrows that again for `0x0042` and `0x0049`: they do not have unique per-type descriptor entries. Both sit inside the same generic descriptor cluster covering `0x003e..0x0050`, and their shared descriptor currently resolves to `psx_spawn_compound_record_advance_state_once`, `psx_object_refresh_main_visible_and_cleanup`, and the newly named `psx_object_release_to_free_list`. So the missing art rule is even less likely to be a special-case table fork and more likely to live downstream in generic object state/resource presentation.
|
||||
- That runtime bridge is now wider and better grounded than it was when `FUN_8002906c` first became the top target. The newly named cluster around it shows that type-4 delayed interactions can actively rewrite the live script after spawn: `psx_type4_update_delayed_interaction` (`0x80029c20`) seeds a forward-probe delay, `psx_type4_reselect_motion_state` (`0x8002906c`) either hands off to `FUN_80028c94` or recomputes heading from motion state before calling `psx_object_select_state_script`, `psx_object_update_nearby_interactions` (`0x80029478`) is the broader non-type-4 nearby-object sweep, `psx_object_test_overlap_3d` (`0x80028298`) is the box-overlap predicate, `psx_object_update_contact_block_flags` (`0x800289f0`) updates directional contact/block bits, and `psx_object_register_contact_pair` (`0x8002845c`) links both objects into the bilateral contact queue.
|
||||
- That runtime bridge is now wider and better grounded than it was when `psx_type4_reselect_motion_state` first became the top target. The newly named cluster around it shows that type-4 delayed interactions can actively rewrite the live script after spawn: `psx_type4_update_delayed_interaction` (`0x80029c20`) seeds a forward-probe delay, `psx_type4_reselect_motion_state` (`0x8002906c`) either hands off to `psx_object_reselect_state_from_target_vector` or recomputes heading from motion state before calling `psx_object_select_state_script`, `psx_object_update_nearby_interactions` (`0x80029478`) is the broader non-type-4 nearby-object sweep, `psx_object_test_overlap_3d` (`0x80028298`) is the box-overlap predicate, `psx_object_update_contact_block_flags` (`0x800289f0`) updates directional contact/block bits, and `psx_object_register_contact_pair` (`0x8002845c`) links both objects into the bilateral contact queue.
|
||||
- The motion-heading piece is now named too. `psx_object_reselect_state_from_target_vector` (`0x80028c94`) writes a target-relative motion vector into `obj+0x60/+0x64/+0x68`, `psx_object_quantize_motion_heading16` (`0x8003bc1c`) wraps the current object motion vector, and `psx_quantize_vector_heading16` (`0x8003b980`) reduces that vector to one of 16 heading buckets before the caller maps it back to a `DAT_800758cc` script selector. Combined with the already-verified `psx_object_advance_state_script -> psx_object_lookup_variant_entry` path, this means the art-facing `DAT_800758d4` lookup is driven by the current script word at `obj+0x94`, not directly by the original placement selector stored at `obj+0x9e`.
|
||||
- The local render-routing wrappers are named now too, which sharpens the practical exporter story. `psx_spawn_compound_record_advance_state_once` (`0x80013618`) and `psx_spawn_simple_record_set_active_flag` (`0x8001372c`) are constructor-side wrappers that immediately push new objects into specific live states, while `psx_object_refresh_main_visible_and_cleanup` (`0x80013688`) is a compact stage-1 projector/cleanup wrapper and `psx_object_advance_state_and_queue_special_visible` (`0x80013758`) is a compact stage-2 wrapper that advances script state and immediately queues through `psx_project_object_special_visible_queue`. So the executable already contains small dedicated routing helpers for “advance state, then main visible” versus “advance state, then special visible”, not just one monolithic render path.
|
||||
- The owner-level bridge is named now too: `psx_object_integrate_motion_and_route_visible` (`0x800131a8`) integrates per-object motion, refreshes visibility flags, then advances script state and routes the object into either the stage-1 main visible list or the stage-2 special-visible queue. That means the runtime split relevant to the viewer is not a late draw-only distinction; it is already baked into the per-object motion/update step.
|
||||
- The drawable-resource side is much tighter now too. Both constructors resolve the per-type art bank and store the resulting drawable resource at `obj+0x10` before the object enters the live update loop; the current script word at `obj+0x94` is then passed straight through `psx_resource_frame_origin_x/y`, `psx_resource_frame_width/height`, and finally into `psx_sprite_resource_submit_frame` or `psx_image_table_submit_frame` during the stage-1 and stage-2 draw passes. So the renderer now has a much stronger executable-backed chain from type bank -> object resource pointer -> live frame index -> final primitive submission.
|
||||
- The builder under that constructor-side handoff is named too: `psx_create_image_resource_from_descriptor` creates the drawable resource from the per-type art descriptor, using `image_resource_bind_vram_slot` for single-image type-4 resources and `image_bundle_load_to_vram` for multi-frame type-5 bundles. That means the remaining viewer gap is no longer “where does the object resource pointer come from”; it is the last live state/variant rule that determines which resource/frame combination unresolved families actually present.
|
||||
- The stage-1 list machinery is named end to end as well: `psx_main_visible_list_add`, `psx_main_visible_list_remove`, `psx_main_visible_list_rebucket_object`, `psx_main_visible_list_refresh_from_live_chain`, `psx_main_visible_list_sort_range`, and `psx_main_visible_list_get_sorted_slice` are the concrete helpers behind the main visible-object pass that `psx_draw_world_visible_passes` submits before the stage-2 queue.
|
||||
- Practical consequence for the viewer plan: the exported placement selector is now firmly only an input, not a final art choice. For unresolved families the next bridge must model at least part of the post-spawn interaction/reselection lane before a `type -> frame` rule can be trusted, otherwise the viewer will keep drawing structurally correct but still unreadable placeholder-heavy scenes.
|
||||
- The main-visible ordering rule is no longer safely approximated as a flat `screenY` sort. `psx_main_visible_list_get_sorted_slice` refreshes only the stage-1 list before `psx_draw_world_visible_passes`, and `psx_main_visible_list_sort_range` then walks dependency counters, class buckets, and world-extent tie-breakers before `psx_draw_main_visible_object` submits frames. A viewer that only projects coordinates and sorts by one screen axis will still miss part of the executable's coherence model even when art binding is otherwise correct.
|
||||
- The level-load side is part of the same coherence chain too. `psx_level_session_loop` calls `wdl_resource_bundle_load_by_index` for the selected `SPEC_A.WDL`/`LSET*.WDL` pair, that load path applies `level_palette_header_apply`, and `level_palette_header_apply` immediately calls `level_palette_upload_cluts`. So the runtime CLUT tables consumed later by `psx_sprite_resource_submit_frame` and `psx_image_table_submit_frame` are installed during level load before any world-object draw routing begins.
|
||||
- The remaining repeated-wall outlier is now tighter in cache evidence too. On `map 104`, the dominant bad cluster is still `type=0x0042`, and the current scene cache repeatedly shows it binding `cross-map-cc-signature-donor:85:0040` with `template_type=64`, `donor_type=64`, `bundle_offset=0x0009d304`, and `requested_palette_index=0 resolved_palette_index=0`. That means the current visible failure there is not primarily “wrong palette variant picked from a recovered family”; it is still the wrong runtime family/resource presentation rule for that unresolved root-dispatch band.
|
||||
- The same evidence now sharpens the next repair rule too: keep authored family identity and raw `u5` class visible until the executable proves a shared resource path. `map 104` currently shows that matching only on donor template/signature is broad enough to merge `section0_constructor_placements` and `section0_dispatch_roots` into the same wall art even when their current runtime role is still unproven.
|
||||
- Practical consequence for the viewer plan: the exported placement selector is now firmly only an input, not a final art choice. The immediate placeholder problem is removed for the focused exporter path, but the next bridge still must model post-spawn interaction/reselection well enough to replace `cohort-*` and `emergency-global-donor:*` provenance with tighter executable-backed family/resource/frame rules.
|
||||
- `JL-9` already appears in recovered PSX weapon-name tables, but gameplay availability and sprite identity are not yet closed.
|
||||
|
||||
## Success Criteria
|
||||
|
|
@ -106,6 +141,8 @@ Tasks:
|
|||
5. Recover the bundle/frame binding rule for map placements well enough to stop relying on broad candidate pairing.
|
||||
6. Recover the draw-order or layer rule used when multiple map records overlap.
|
||||
7. Validate the corrected multi-section schema on at least `L0.WDL` and `L1.WDL` so the decode is not overfit to one level.
|
||||
8. Dump the installed section-pack/runtime tables after level load for `map 104`, especially `psx_ctor_placement_section_ptr`, `psx_type_policy_table_ptr`, `level_clut_table`, and the `0x80063e54/0x80063e68/psx_marker_channel_runtime_block` family.
|
||||
9. Correlate those live table rows against the fixed `map 104` `0x0042` sample pack (`item:25/30/31/35/85/86`) before widening any donor or family heuristics again.
|
||||
|
||||
Expected output:
|
||||
|
||||
|
|
@ -182,13 +219,39 @@ Expected output:
|
|||
3. Compare the rebuilt all-map exports against recognizable rooms and decide whether the remaining missing structure now lives mainly in the decoded `DAT_8006769c` buffer or in still-unrendered subordinate tables.
|
||||
4. Tighten the raw file mappings for the newly exported runtime-bank layers (`DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`) so their current section selection is proven rather than heuristic.
|
||||
Current delta: the bank split is stronger now. `DAT_800758d8` comes from its own late descriptor stream, while `DAT_800758d0/cc/d4` belong to the adjacent `psx_load_type_state_banks` blobs. The remaining open question is the exact sub-split inside those state-bank blobs, not whether they come from the decompressed level-state lane.
|
||||
5. Recover an actual bundle/frame reference from the per-type template payloads or their consumers so the exporter can replace the now-disproven scan-order bundle fallback with a verified type-to-art rule.
|
||||
Current delta: the template bank selection is now stronger and already recovers real art for a first subset, but the still-missing families need the stage-1/stage-2 object draw path plus `DAT_800758cc/d4` state interpretation, not more HUD/overlay decoding.
|
||||
Current delta: stage 2 is no longer hypothetical. The next renderer-improvement candidate is to expose/export the queued-object lane that feeds `FUN_80041144`, because the executable now clearly maintains it separately from the main visible list.
|
||||
Current delta: the unresolved families are also clearly dynamic now because `psx_object_lookup_variant_entry` reruns after `psx_object_advance_state_script`; the next verified art-binding pass should therefore sample post-jump state, not only constructor-time selectors.
|
||||
6. Split section-0 placements into at least three executable-backed render classes: world-facing geometry/object placements, animated runtime-only objects, and clearly non-map-facing UI/talk assets such as the portrait bundles currently surfacing through fallback art matching.
|
||||
7. Decode the `psx_object_advance_state_script` sentinel opcodes (`ffff`, `fffe`, `fffd`, `fffc`, `fffb`) well enough to tell when a placement loops, jumps into a subsidiary script, or fires a side-effect helper, because that state-machine branch is now the main discriminator between map-facing art and non-map runtime assets.
|
||||
Current delta: `fffe` is closed as an audio/effect dispatch through `FUN_8004061c`, `fffd` is the direct indexed jump, and `fffc/fffb` are now separated as the immediate subsidiary-switch versus scan-forward subsidiary-switch pair.
|
||||
5. Split the remaining art/palette problem by render lane and resource class before widening donor logic again. For each repeated-wall family, determine whether it reaches `psx_draw_main_visible_object` or `psx_draw_special_visible_queue`, whether its bound resource at `obj+0x10` is sprite-kind or image-table-kind, and whether the authored override byte should be interpreted through the main visible override path at all.
|
||||
Current delta: `psx_draw_main_visible_object` now proves a banded override rule (`0x003e..0x00ab -> source+0x06 high byte`, `>=0x00ac -> source+0x0c high byte`), while `psx_draw_special_visible_queue` skips that authored override entirely.
|
||||
Current delta: `psx_sprite_resource_submit_frame` and `psx_image_table_submit_frame` both accept the same high-byte override token but resolve it through different CLUT tables, so the exporter must stop treating palette selection as a type-only scalar.
|
||||
Current delta: the constructors already bind the drawable resource from `DAT_800758d8` before any later state transitions, so the remaining aliasing is not best attacked as a missing late bundle lookup.
|
||||
Current delta: the worst visible outlier on `map 104` is still `type=0x0042` repeatedly mapped to donor map `85` type `0x0040` bundle `0x0009d304` with palette `0`, so the next repair pass should treat it first as a wrong family/resource binding problem, not as a missing alternative palette decode.
|
||||
Current delta: the current donor pass is broad enough to merge `section0_constructor_placements u5=0x0020` and `section0_dispatch_roots u5=0x0030/0x0022` onto that same donor wall, so the next exporter-safe experiment should keep family plus raw `u5` as a hard fence before broadening any cross-map donor reuse further.
|
||||
Current delta: that hard fence is now implemented in the viewer cache builder for provisional generic-family donor matches. The cache now prefers honest placeholders over one shared wrong wall when a single type bucket mixes authored families or `u5` classes.
|
||||
Current delta: the new subagent sweep says the next split should not be “invent a new per-type descriptor bucket for `0x0042`,” because `0x0042` still shares the generic `0x003e..0x0050` descriptor cluster. The next executable-backed discriminator should be runtime-bank content, state progression, resource kind, and lane/flag behavior instead.
|
||||
6. Recover an actual family-specific state-to-frame rule from the post-spawn runtime path so the exporter can replace the still-provisional `state_selector -> chosen_frame` fallback with the live `obj+0x94` path used by the submitters.
|
||||
Current delta: the unresolved families are clearly dynamic because `psx_object_lookup_variant_entry` reruns after `psx_object_advance_state_script`, `psx_object_reselect_state_from_target_vector`, and `psx_type4_reselect_motion_state`; the next verified art-binding pass should therefore sample post-jump state, not only constructor-time selectors.
|
||||
Current delta: the executable's coherent-map path is now ordered as load-time CLUT install -> constructor-side resource bind -> per-frame motion/state advance -> stage-1 or stage-2 visibility routing -> stage-1 dependency sort -> frame submission. Any next exporter shortcut should be checked against that order before being treated as a stable map rule.
|
||||
Current delta: draw-time submitter choice is now explicit too. `psx_draw_main_visible_object` and `psx_draw_special_visible_queue` both choose `psx_image_table_submit_frame` only when the bound resource header kind is `5`; otherwise they use `psx_sprite_resource_submit_frame`. So unresolved `0x0042` still needs runtime resource-kind evidence, not just lane or type labels.
|
||||
Current delta: treat the HUD/overlay lane as a false lead for this specific repair. `psx_draw_hud_overlay_pass` can route through slot flags rather than the normal world-object kind split, and `psx_draw_clock_digits_overlay` is forced image-table presentation. Neither should be used to infer `0x0042` world-object art binding.
|
||||
Current delta: the next executable-backed sample should be concrete, not another broad sweep. For `map 104`, trace at least one constructor-placement `type=0x0042 u5=0x0020` case and one root-dispatch `type=0x0042 u5=0x0030` case through `obj+0x10` resource kind, `obj+0x94` live script word, and final world-facing lane so the exporter can decide whether these are truly separate presentation families.
|
||||
Current delta: the concrete sample pack is now fixed. Use `item:25`/`35` as the root `0x0022` control, `item:30`/`31` as the root `0x0030` control, and `item:85`/`86` as the constructor `0x0030` control before widening any art-binding experiment.
|
||||
Current delta: include `obj+0x1c` in that concrete sample. The next trace should explicitly record whether bit `0x0400` is ever set for representative `map 104` `0x0042` objects, because that is now the strongest recovered stage-2 selector and is more decisive than raw `u5` by itself.
|
||||
Current delta: include the per-type transition-table row in that same sample. For `type 0x0042`, inspect the `DAT_80063b4c` row used by `psx_object_select_state_from_transition_table` and compare its selector outputs against the observed `obj+0x9e` / `obj+0x94` values; that is now the clearest route to explaining selector `3/4` cases without reopening broad donor heuristics.
|
||||
Current delta: do not stop at the latched `obj+0x94` value. For representative `0x0042` cases, sample the pre-latch selector dispatch path through `psx_type42_transition_selector_tick` as well; selector `3/4` can be selected there before the runtime latch copy, so a pure `obj+0x94` snapshot can still miss the active turn-state choice.
|
||||
Current delta: treat `DAT_800675f8` as policy, not as the main route discriminator. `0x1000` is now best read as nearby-publication suppression after stage-2 routing, `0x0600` as stage-1 ordering class, and `0x2000` as main-visible semitrans policy. The next `0x0042` split still belongs first to `obj+0x1c`, pre-latch selector behavior, and bound resource kind.
|
||||
Current delta: include the early-gate side in that same sample. `psx_type42_transition_selector_tick` now clearly checks `psx_object_is_within_view_margin` before the pre-latch turn/reseat dispatch, so the next runtime sample should log whether the object is even eligible to emit the transient selector before trying to explain a final frame choice.
|
||||
Current delta: keep the `0x0400` question scoped correctly. The latest anonymous-island recovery proves wider runtime-state writes and related policy control, but not yet a direct object-local `obj+0x1c |= 0x0400` writer. So the next pass should explicitly separate object-local `obj+0x1c`, nested runtime state words, and global mode bits instead of folding them into one generic “route flag” bucket.
|
||||
Current delta: treat `runtimeDiagnostic.typePolicy.word` as a level-global per-type value when you do capture it. The latest pass now closes the table role more tightly than before: `DAT_800675f8` is a level-loaded type-policy pointer, not a per-lane word source.
|
||||
Current delta: use the new cache export as the comparison surface. The next batch should target concrete `mapSource.items[*].runtimeDiagnostic` channels in the generated PSX scene JSON and tie live Ghidra evidence back to those exact fields, especially for representative `map 104` `0x0042` placeholders.
|
||||
Current delta: the next exporter/runtime correlation should add a stable bound-resource identity plus the live frame/state token used at submission if the current fields are not enough. The concrete `0x0042` sample pack now strongly suggests that `0x0022` versus `0x0030` is not itself the final `64x40` versus `64x64` split.
|
||||
Current delta: the route branch itself is no longer vague. `psx_object_integrate_motion_and_route_visible` now has an explicit `obj+0x1c & 0x0400` stage-2 branch point commented in the live database, so the next runtime capture does not need to hunt for the branch location, only to determine whether the concrete sample items ever carry that bit at the decisive moment.
|
||||
7. Split section-0 placements into at least four executable-backed presentation classes: main-visible objects with authored palette override, special-visible objects without that override, animated runtime-only objects, and clearly non-map-facing UI/talk assets such as the portrait bundles currently surfacing through fallback art matching.
|
||||
8. Decode the `psx_object_advance_state_script` sentinel opcodes (`ffff`, `fffe`, `fffd`, `fffc`, `fffb`) well enough to tell when a placement loops, jumps into a subsidiary script, or fires a side-effect helper, because that state-machine branch is now the main discriminator between map-facing art and non-map runtime assets.
|
||||
Current delta: `fffe` is closed as an audio/effect dispatch through `psx_script_dispatch_audio_event`, `fffd` is the direct indexed jump, and `fffc/fffb` are now separated as the immediate subsidiary-switch versus scan-forward subsidiary-switch pair.
|
||||
9. Recheck the renderer-side palette export against those executable rules before another broad cache rebuild. In particular, verify that map-scene palette variants only use authored override bytes for the type bands and render lanes where `psx_draw_main_visible_object` actually consumes them, and keep special-visible families on their resource-default path until a stronger rule is proved.
|
||||
|
||||
Current delta: live function coverage is now measurable instead of anecdotal. The active `SLUS_002.68` session currently has `1274` local `0x800...` functions, `917` named and `357` still anonymous, which puts the practical local naming floor at `71.98%` and leaves `28.02%` still unresolved. The hottest remaining anonymous pages are currently `0x8002exxx`, `0x80030xxx`, and `0x80049xxx` with `21` unknowns each.
|
||||
|
||||
Current delta: one adjacent world/update family is now tighter too. The `0x80023c..2b1..` lane is no longer just a pile of anonymous helpers: `psx_object_run_control_opcode` now owns the local control-stream switch, case `1` is split into `psx_control_move_player_to_point` and `psx_control_move_object_to_point`, case `3` is `psx_control_wait_ticks`, cases `4/5` are now closed as `psx_control_configure_fixed_camera_anchor`, case `9` is `psx_control_set_facing_direction`, and case `2` is grounded as a deferred control-command queue (`psx_queue_deferred_control_command` -> `psx_flush_deferred_control_queue` -> `psx_apply_deferred_control_command` -> dispatch-root/live-object appliers). Case `8` is still not named at the wrapper level, but its direct callee is now closed as `psx_spawn_object_compound_effect_variant3`, which narrows the remaining uncertainty to the wrapper's timing and motion-side effects rather than the spawned effect itself.
|
||||
8. In parallel with the map pass, trace the palette-override read path from the known draw helper caller and document which source field feeds the resolved CLUT.
|
||||
9. Locate the `JL-9` weapon entry in the PSX executable tables and log its table index, surrounding weapon names, and all code/data xrefs.
|
||||
10. Create a short follow-up note in `docs/psx/` after the batch rather than burying the result only in Ghidra comments.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,38 @@
|
|||
- Target disc tree: `E:\emu\psx\Crusader - No Remorse`
|
||||
- Goal of this pass: identify the boot executable, separate likely code from content, and find the most practical first extraction routes for PS1 assets.
|
||||
|
||||
## Function Coverage Census
|
||||
|
||||
Current live census for the active Ghidra `SLUS_002.68` session:
|
||||
|
||||
- Raw function table: `1428` total, `1070` named, `358` still anonymous (`74.93%` named)
|
||||
- Local executable code only (`0x800...` addresses): `1274` total, `917` named, `357` still anonymous (`71.98%` named, `28.02%` anonymous)
|
||||
|
||||
Method used:
|
||||
|
||||
- queried the live MCP `list_functions` endpoint against the active PSX program
|
||||
- counted `FUN_` and `nullfn_` names as anonymous placeholders
|
||||
- treated the `0x800...` address range as the practical coverage metric so imported/system helpers in other spaces do not distort the remaining-work count
|
||||
|
||||
Current hottest anonymous local windows by `0x1000` page are:
|
||||
|
||||
- `0x8002exxx`: `21`
|
||||
- `0x80030xxx`: `21`
|
||||
- `0x80049xxx`: `21`
|
||||
- `0x80031xxx`: `17`
|
||||
- `0x80040xxx`: `14`
|
||||
- `0x8002bxxx`: `14`
|
||||
- `0x8003axxx`: `13`
|
||||
- `0x80020xxx`: `13`
|
||||
- `0x80048xxx`: `13`
|
||||
- `0x80046xxx`: `11`
|
||||
|
||||
Practical read:
|
||||
|
||||
- the PSX database is well past the early blind-mapping stage; roughly seven tenths of the local code already has non-placeholder names
|
||||
- the remaining anonymous mass clusters in a few rendering, world-update, and resource-heavy windows rather than being evenly spread
|
||||
- `0x80040xxx` remaining hot is still consistent with the current map-rendering blocker, because that window contains late projection and presentation helpers rather than only loader-side code
|
||||
|
||||
## Immediate Conclusions
|
||||
|
||||
- `SYSTEM.CNF` is the disc boot file and points directly at `cdrom:\SLUS_002.68;1`.
|
||||
|
|
@ -172,7 +204,7 @@ Current best read:
|
|||
Executable-guided extraction status:
|
||||
|
||||
- `lset_level_bundle_load` in the imported PSX executable now confirms the executable builds `\LSETn\Lx.WDL` paths directly and treats those files as the live level-bundle format.
|
||||
- The same loader reads a small level header blob first, then a large SPU/audio blob, then dispatches the remaining level resource stream through `level_resource_stream_load`.
|
||||
- The same loader reads a small level header blob first, then a large SPU/audio blob, then dispatches the remaining level resource stream through `psx_stream_install_type_runtime_banks`.
|
||||
- `image_resource_bind_vram_slot` and `image_bundle_load_to_vram` show that resource types `4` and `5` are image/sprite-oriented resources: they resolve VRAM placement and upload image data through `LoadImage`.
|
||||
- `sprite_rle_decode_rows` is now confirmed as the row-based decompressor used when a type-5 frame record has its compressed bit set.
|
||||
- Current consequence: sprite extraction now has a real executable-backed path, while map extraction has a reliable raw-carving path even though the full tile/object semantics are not decoded yet.
|
||||
|
|
@ -531,7 +563,7 @@ Current invalidation result:
|
|||
|
||||
- this direct `u0 -> bundle index` mapping is now considered invalid for real renderer output
|
||||
- the produced scene repeats a small set of obviously wrong assets, including portrait/UI-like art, in places that do not make spatial sense for the map
|
||||
- executable-side tracing shows that art selection is type-driven through `DAT_800758cc/d0/d4/d8` resource tables loaded by `level_resource_stream_load`, not by directly indexing the raw `post_audio_region_04` bundle scan
|
||||
- executable-side tracing shows that art selection is type-driven through `DAT_800758cc/d0/d4/d8` resource tables loaded by `psx_stream_install_type_runtime_banks`, not by directly indexing the raw `post_audio_region_04` bundle scan
|
||||
|
||||
New loader/data evidence from this pass:
|
||||
|
||||
|
|
@ -569,7 +601,7 @@ Current app compatibility notes:
|
|||
Immediate implications for the next decode pass:
|
||||
|
||||
- the public renderer integration path is now proven enough to use as a live debug target for PSX map-format work
|
||||
- the next priority is to replace the invalidated `u0 -> bundle index` hypothesis with a real type/resource lookup recovered from `level_resource_stream_load`
|
||||
- the next priority is to replace the invalidated `u0 -> bundle index` hypothesis with a real type/resource lookup recovered from `psx_stream_install_type_runtime_banks`
|
||||
- `post_audio_region_00` is now a top-tier candidate for that work because its new diagnostics expose a count-prefixed preamble plus compact typed records that look more loader-compatible than the old region-01 art probe
|
||||
- the palette override path is now partially landed in the viewer/exporter too: the cache builder applies the executable-backed authored override byte when the source record exposes the proven `+0x06` / `+0x0c` lane, so the remaining blocker is the cases where the runtime first picks a different object/variant/state than the current export model
|
||||
- once the bundle key and palette control path are recovered, the same scene-export path can graduate from `real-art probe` to actual PSX map rendering
|
||||
|
|
@ -650,6 +682,8 @@ What the loader actually does:
|
|||
- `type = record[+0x08]`
|
||||
- `dispatch through PTR_PTR_80063118[type]`
|
||||
- Those dispatch handlers do not behave like a terrain-tile walker. They construct one runtime object or a tiny object cluster at a time through `FUN_800249f4`, `FUN_80024eec`, `FUN_8003c314`, `FUN_8003c714`, and `FUN_8003cc08`.
|
||||
- The current `0x0042` result now fits that same generic-object model more tightly than before: `psx_type_descriptor_table[0x0042]` at `0x80063220` points to the shared row `0x800626f8`, so both constructor-placement and root-dispatch `0x0042` still enter the same generic create/update/release family rather than a unique descriptor fork.
|
||||
- The selector side is tighter too: type `0x0042` now has a dedicated transition helper, `psx_type42_transition_selector_tick`, that can dispatch low turning selectors `3/4` before the `+0x94`-style runtime latch copy. So unresolved `0x0042` presentation is no longer best framed as only a missing descriptor-table or raw-selector decode problem; part of the remaining gap is that the live turn/reseat path can be ahead of the currently latched script word.
|
||||
|
||||
Why the current export is incoherent:
|
||||
|
||||
|
|
@ -659,7 +693,7 @@ Why the current export is incoherent:
|
|||
|
||||
New executable-backed evidence for the missing bulk content:
|
||||
|
||||
- `level_resource_stream_load` and `FUN_8003917c` populate the typed runtime resource tables rooted at `DAT_800758cc/d0/d4/d8`
|
||||
- `psx_stream_install_type_runtime_banks` and `FUN_8003917c` populate the typed runtime resource tables rooted at `DAT_800758cc/d0/d4/d8`
|
||||
- `DAT_80067720` is a small top-level `0x18` record list used by object/event-style helpers such as `FUN_80031044` and `FUN_8002b1a8`; it is not a whole-map terrain stream
|
||||
- during bundle load, `FUN_8003b00c(DAT_8006769c, &DAT_8006b5d8, 0x3e00, 0x3e00)` inflates a separate compressed blob into a dedicated level buffer
|
||||
- that decompressed buffer is carried through save/load helpers (`FUN_8003a0f4`, `FUN_80049890`) independently of the tiny top-level descriptor list, which is exactly what a real map substrate would do
|
||||
|
|
@ -698,7 +732,7 @@ Exporter status after the next renderer pass:
|
|||
- the renderer-side reference payload no longer emits one atlas per resolved PSX shape. The new packed-atlas pass reduces the shared PSX reference cache from the old `4032` one-shape atlases to `1925` shared packed atlases across the same `4032` shape definitions, and a spot-check on `LSET1/L0.WDL` now exports the map scene itself with `atlasCount = 1` instead of a long per-bundle atlas list.
|
||||
- the cache export now carries more than the parsed `DAT_800758d8` candidate section. In the current `psx-runtime-record-probe-v6` scene path, `map_renderer/src/lib/psx-cache.js` serializes `DAT_800758cc`, `DAT_800758d0`, `DAT_800758d4`, and the offline `FUN_8003b00c` decode candidate for `DAT_8006b5d8 -> DAT_8006769c` into `stateLayers`, and the scene writer preserves those layers in both scene metadata and `mapSource`.
|
||||
- the new typed-section-16 discovery path is also broader than the earlier section-start probe: when no parsed-section candidate wins, the cache builder now falls back to an absolute-file scan, which is why the late compound-bank blobs can now land in the export even when their serialized source does not start exactly at a pre-labeled section boundary.
|
||||
- the file-side header block now separates more cleanly too: `FUN_80039c40` allocates a `0x50` level runtime-header block at `DAT_80067794`, and `FUN_80039dc4` copies that block into globals such as `DAT_80078ab0`, `DAT_80078a88`, `DAT_80078a8c`, `DAT_80078a4c`, and `DAT_80067354` before calling `FUN_80042ec4`. So the first visible/root sections are not the only authoritative level metadata; the loader also applies a dedicated `0x50` per-level runtime header after the optional `0x3e00` decode succeeds.
|
||||
- the file-side header block now separates more cleanly too: `FUN_80039c40` allocates a `0x50` level runtime-header block at `DAT_80067794`, and `psx_apply_level_runtime_header_block` copies that block into globals such as `DAT_80078ab0`, `DAT_80078a88`, `DAT_80078a8c`, `DAT_80078a4c`, and `DAT_80067354` before calling `FUN_80042ec4`. So the first visible/root sections are not the only authoritative level metadata; the loader also applies a dedicated `0x50` per-level runtime header after the optional `0x3e00` decode succeeds.
|
||||
- this still does not mean the PSX map decode is fully solved: the viewer now has enough volume to represent whole-level candidates across the disc, but the remaining blocker is semantic decoding of the subordinate runtime banks and the separate decompressed `0x3e00` buffer, not record-count starvation.
|
||||
- the type-to-art path is only partially improved. The cache builder now scans the parsed per-type art-template payloads for bundle references, and the renderer no longer treats the disproven scan-order `u0 -> bundle` mapping as trustworthy visible art. Unverified types now stay on placeholder art instead of surfacing known-bad portrait/talk bundles as map geometry.
|
||||
- the scan-order fallback is now known to be wrong at the root, not merely incomplete. In the live `.cache` output, `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles such as map `0` type `0042` -> offset `0x000B2970` and map `0` type `0049` -> offset `0x000D84F4`, with the same failure pattern continuing through early maps. Those portrait bundles are useful negative evidence: they show the top-level dispatch rows are generic object/state descriptors, not a direct map-graphics stream that can be paired to bundle order.
|
||||
|
|
@ -733,13 +767,13 @@ Next decoded runtime layers from the constructor pass:
|
|||
- `psx_object_lookup_variant_entry` is not only a constructor-time helper. Its call graph now shows three direct consumers: `psx_object_create_simple_record`, `psx_object_create_compound_record`, and `psx_object_advance_state_script`. That means unresolved families cannot be modeled as one spawn-time `type -> variant` choice; the visible companion bytes can be recomputed after state-script control flow advances.
|
||||
- The current renderer-side consequence is important: section-0 word `u4` is no longer treated as a verified sprite-frame index. It is now carried forward as a state-selector candidate in exported scene metadata until the `DAT_800758cc/d4` path is decoded far enough to pick the right animation frame from executable evidence.
|
||||
- Current strongest sentinel read:
|
||||
- `0xfffe` dispatches `FUN_8004061c`, which is an audio/effect helper rather than a visible-frame selector.
|
||||
- `0xfffe` dispatches `psx_script_dispatch_audio_event`, which is an audio/effect helper rather than a visible-frame selector.
|
||||
- `0xfffd` is an in-script jump/re-anchor control that rewrites `obj+0x90` relative to the current script base.
|
||||
- `0xfffc` resolves a fresh subsidiary script base from the table rooted at `obj+0x88`, then immediately swaps both `obj+0x8c` and `obj+0x90` to that destination before continuing from the first record there.
|
||||
- `0xfffb` also resolves a subsidiary script from the same table, but first walks the current script forward until it finds an in-band `0xfffd` marker and then uses that marker's selector word to choose the destination entry.
|
||||
- Current best read of those sentinels:
|
||||
- `0xffff` marks a terminal or restart control that re-anchors the script at `obj+0x8c` and raises object-state flags.
|
||||
- `0xfffe` dispatches a side-effect helper (`FUN_8004061c`) using the following word as a parameter before advancing.
|
||||
- `0xfffe` dispatches a side-effect helper (`psx_script_dispatch_audio_event`) using the following word as a parameter before advancing.
|
||||
- `0xfffd` is the direct indexed jump control within the current script family.
|
||||
- `0xfffc` and `0xfffb` are both subsidiary-script switches through the `DAT_800758cc` offset table rooted at `obj+0x88`, but `0xfffb` is specifically the scan-forward variant that consumes the next in-band `0xfffd` selector.
|
||||
- because `psx_object_advance_state_script` calls `psx_object_lookup_variant_entry` after those control-flow steps, the visible art choice for unresolved types may depend on post-jump script state rather than on the placement selector byte alone.
|
||||
|
|
@ -761,7 +795,7 @@ Next decoded runtime layers from the constructor pass:
|
|||
- Current status note: even with the recovered placement/projection path and the first subset of real bundle-backed types, the live PSX map output is still unreadable as a practical map because most section-0 placements still miss the executable's final state/variant-driven art binding and therefore collapse back to placeholders.
|
||||
- The next loader-side correction is now verified in the live cache too: the effective late `LSET*.WDL` `DAT_800758d8` candidate is not the earlier small-section heuristic, but a large late section whose working descriptor stream begins at an embedded `+0x38` offset. On retail map `9` that correction alone lifts `bundleMappedItemCount` from `0` to `111`, which is enough to restore real bundle-backed art for a first subset of types without reintroducing the disproven scan-order fallback.
|
||||
The still-unresolved root-dispatch families remain instructive rather than contradictory. `0x0042` and `0x0049` still stay on placeholders after the bank-selection fix, but the same pass now decodes their `DAT_800758cc` state rows more cleanly: type `0x0042` carries three selector-targeted scripts (`0`, `1`, `2`) that all terminate through `0xffff`, while type `0x0049` carries a single selector-`0` script. The built scene cache shows that this is still not the whole art-facing discriminator: `type=0x0042` placeholders now appear with selectors `0..4`, and the higher selectors `3` and `4` are real exported cases rather than parser noise. Verified maps with `0x0042` selectors above `2` include `map-4`, `map-5`, `map-8`, `map-45`, `map-69`, and `map-85`.
|
||||
Two runtime reselection paths now explain how those higher selectors can arise without contradicting the earlier three-script file read. `FUN_80028c94` and `FUN_8002906c` both recompute the active script with `psx_object_select_state_script(obj, FUN_8003bc1c(obj) >> 2 & 0xf)`, where `FUN_8003bc1c` quantizes the object's current motion vector at `obj+0x60/+0x64` through `FUN_8003b980` into a 16-way heading bucket. So cache-visible `0x0042` selectors `3` and `4` can come from runtime heading/state reselection, not only from the original placement byte.
|
||||
Two runtime reselection paths now explain how those higher selectors can arise without contradicting the earlier three-script file read. `psx_object_reselect_state_from_target_vector` and `psx_type4_reselect_motion_state` both recompute the active script with `psx_object_select_state_script(obj, psx_object_quantize_motion_heading16(obj) >> 2 & 0xf)`, where `psx_object_quantize_motion_heading16` quantizes the object's current motion vector at `obj+0x60/+0x64` through `psx_quantize_vector_heading16` into a 16-way heading bucket. So cache-visible `0x0042` selectors `3` and `4` can come from runtime heading/state reselection, not only from the original placement byte.
|
||||
That cache sweep also separates selector from lane more clearly than before. `0x0042` appears heavily on lanes `0x0020` and `0x0022`, and there are also map-local `lane=0x0030` cases (for example large clusters on `map-108`) that still export `state_selector=0`. So the unresolved bridge is narrower now: the visible-art rule cannot be modeled as just `u5` or just the initial `DAT_800758cc` selector parse. The remaining unknown is the downstream interaction between `u4`/`obj+0x9e`, the active state-script pointer at `obj+0x8c/0x90`, and the `DAT_800758d4` companion lookup that reruns after state-script advancement.
|
||||
A first renderer-safe bridge landed even with that exporter gap still open: the verified `0x0050` state-script mapping (`selector 0..3 -> frame 0..3`) is now applied as a narrow fallback in the cache builder, and the rebuilt live map-9 scene now shows `type=80 state_selector=1 chosen_frame=1` instead of the old forced `chosen_frame=0`. Unresolved fallback placeholders are also now clamped to `opacity=0.45` in live scene output so the still-missing families stop visually overpowering the recovered real art. This remains intentionally scoped: the fallback frame map only covers the one family with direct executable-backed frame evidence, and the opacity clamp is diagnostic relief rather than a decoding claim.
|
||||
The current draw split is clearer too. `FUN_80041378` is a three-stage render pass:
|
||||
|
|
@ -778,7 +812,7 @@ Next decoded runtime layers from the constructor pass:
|
|||
- The recovered post-state-advance updater family now splits into five visible call sites: `0x80012b44`, `0x80013524`, `0x80013564`, `0x80013650`, and `0x80013778` all call `psx_object_advance_state_script`.
|
||||
- Three of those sites then feed the main stage-1 projector path through `FUN_80040d44` (`0x80012b60`, `0x8001357c`, `0x800136d4`), while two feed the stage-2 queue-builder path through `FUN_80040f78` (`0x8001352c`, `0x80013780`).
|
||||
- That exact `3` versus `2` split matters because it tightens the earlier claim: stage-2 membership is tied to a narrower runtime object/state branch after state advance, not to the decompressed substrate buffer alone and not to all state-advanced objects indiscriminately.
|
||||
- One state-script sentinel is now functionally closed too: `0xfffe` dispatches `FUN_8004061c`, which is an audio/effect helper rather than a visible-frame selector. That shrinks the unknown sentinel set for the remaining `DAT_800758cc` script work.
|
||||
- One state-script sentinel is now functionally closed too: `0xfffe` dispatches `psx_script_dispatch_audio_event`, which is an audio/effect helper rather than a visible-frame selector. That shrinks the unknown sentinel set for the remaining `DAT_800758cc` script work.
|
||||
- The main visible-list helpers are now also separated cleanly enough to stop treating them as a blocker:
|
||||
- `FUN_8002d240` adds an object to the stage-1 `DAT_8006ad5c` visible-list array.
|
||||
- `FUN_8002d35c` removes an object from that same array.
|
||||
|
|
@ -808,24 +842,36 @@ Additional constructor-backed coordinate grounding from the current pass:
|
|||
|
||||
Recovered per-level runtime-header lane:
|
||||
|
||||
- `FUN_80039c40` is now confirmed as a pure `0x50` allocator for `DAT_80067794`, and `FUN_80039dc4` is the matching applier for that block.
|
||||
- `FUN_80039dc4` copies fixed fields from `DAT_80067794` into the active level globals, including camera/runtime anchor values and several per-level mode bytes, then calls `FUN_80042ec4` to refresh dependent runtime state.
|
||||
- `FUN_80039c40` is now confirmed as a pure `0x50` allocator for `DAT_80067794`, and `psx_apply_level_runtime_header_block` is the matching applier for that block.
|
||||
- `psx_apply_level_runtime_header_block` copies fixed fields from `DAT_80067794` into the active level globals, including camera/runtime anchor values and several per-level mode bytes, then calls `FUN_80042ec4` to refresh dependent runtime state.
|
||||
- The downstream call graph narrows that lane further: `psx_apply_level_runtime_header_block` is the only loader-side caller that feeds those values from WDL data, while `FUN_80042ec4` is also reused by `psx_input_device_init`, `memory_card_menu_tick`, and one additional front-end/system path. Current safest read: `DAT_80067794` is shared per-level runtime mode or presentation state rather than hidden bulk map geometry.
|
||||
- Practical exporter consequence: keep the `DAT_80067794` fields as a first-class raw metadata lane, but do not treat them as a missing placement stream. They are more likely to affect camera/runtime modes, screen-space behavior, or level-global toggles than to supply extra map cells directly.
|
||||
- The higher-level level lifecycle is now readable too. `psx_level_session_loop` is the outer level-session loop: it loads the selected WDL through `wdl_resource_bundle_load_by_index`, applies shared overlay/resource setup through `FUN_800388a8`, resets a small per-level step-flag block with `FUN_8003a498`, and then runs `psx_world_frame_tick` as the per-frame world loop until the current level session exits.
|
||||
- `wdl_resource_bundle_load_by_index` is now mapped tightly enough for viewer work. Its effective order is: load `SPEC_A.WDL` and shared type art/state banks; open the selected `LSET*.WDL`; read the `0x38` section-size header; lay out the contiguous per-level section pack at `DAT_800678f4`, `DAT_80067720`, `DAT_800678f0`, `DAT_80067938`, `DAT_80067838`, `DAT_800675f8`, `DAT_8006754c`, `DAT_80067840`, and `DAT_800676d8`; load the detached `DAT_8006767c` blob; optionally inflate `DAT_8006b5d8` into `DAT_8006769c`; apply the runtime header at `DAT_80067794`; then dispatch the `0x18`-stride root records at `DAT_800678f4` through the per-type function table in `PTR_PTR_80063118`.
|
||||
- The per-frame world loop in `psx_world_frame_tick` is now split clearly enough for renderer planning. In the normal in-level branch it ticks existing live objects through `psx_run_live_object_type_updates`, instantiates or refreshes nearby authored records through `psx_dispatch_section0_dispatch_roots` and `psx_dispatch_section0_constructor_placements`, runs per-object behavior callbacks through `psx_run_live_object_behavior_callbacks`, integrates world/player motion and active-object state through `FUN_80029de0`, updates queued transient resources through `FUN_8002aed0`, and only then submits the draw pass through `FUN_80041378`.
|
||||
- The per-frame world loop in `psx_world_frame_tick` is now split clearly enough for renderer planning. In the normal in-level branch it ticks existing live objects through `psx_run_live_object_type_updates`, instantiates or refreshes nearby authored records through `psx_dispatch_section0_dispatch_roots` and `psx_dispatch_section0_constructor_placements`, runs per-object behavior callbacks through `psx_run_live_object_behavior_callbacks`, integrates world/player motion and active-object state through `psx_update_motion_and_nearby_interactions`, updates queued transient resources through the still-structural `FUN_8002aed0` queue-drain helper, and only then submits the draw pass through `FUN_80041378`.
|
||||
- The two authored record-family passes now line up directly with the viewer exporter model:
|
||||
- `psx_dispatch_section0_dispatch_roots` walks the `DAT_80067720` `0x18`-stride family plus the fixed-size entries at `DAT_80067658`, culls them to roughly a `+/-0x140` neighborhood around the current focus object, and dispatches their per-type handlers. This is the closest executable match for the current `section0_dispatch_roots` viewer family.
|
||||
- `psx_dispatch_section0_constructor_placements` walks the `DAT_800678f0` `0x0c`-stride family with the same neighborhood cull and per-type dispatch. This is the closest executable match for the current `section0_constructor_placements` viewer family.
|
||||
- The already-instantiated-object passes are separated too:
|
||||
- `psx_run_live_object_type_updates` iterates the linked live object list at `DAT_800675ac` and calls the per-type update callback (`type_vtable+8`) for active in-world objects.
|
||||
- `psx_run_live_object_behavior_callbacks` then runs each live object's callback stored at `obj+0x98` / `obj[0x26]`, which is the later object-specific behavior/update pass.
|
||||
- `FUN_80029de0` is the broad world-motion and player-state integrator that sits between behavior updates and draw submission. For viewer purposes, this is the runtime bridge between authored map placement and the motion/state values that later feed heading-based state reselection and projection.
|
||||
- The cull-to-draw bridge is now closed too. `FUN_800423b0` is the authored-record screen-space gate used by the two section-0 dispatch passes, while `FUN_80042424` is the corresponding gate for already-instantiated live objects. Both use the same isometric camera basis around `DAT_800678d4`, which means the viewer can treat the record-family export as feeding the same projection space as the later live object list instead of as a separate map coordinate model.
|
||||
- One adjacent control family in the same world/update lane is now tighter too:
|
||||
- `psx_object_run_control_opcode` (`0x80023c54`) executes one opcode from the object-local control stream at owner `+0x20`.
|
||||
- `psx_control_move_player_to_point` (`0x80023efc`) is control opcode case `1` for the player path: it lazily seeds a target point from the opcode payload, steers toward it through the heading solver, and completes once the player reaches that point.
|
||||
- `psx_control_move_object_to_point` (`0x80024070`) is the non-player version of the same case `1` path, using the object movement-state helper to keep facing and locomotion aligned while the object advances toward the opcode target.
|
||||
- `psx_queue_deferred_control_command` (`0x800241f4`) appends deferred control-command entries into the small queue rooted at `DAT_8008f608` / `DAT_8008ef90` with count `DAT_80067730`.
|
||||
- `psx_control_wait_ticks` (`0x80024290`) is control opcode case `3`: a one-shot timed wait gate that snapshots `DAT_80078a28` on first entry and completes only after the opcode's tick count has elapsed.
|
||||
- `psx_control_configure_fixed_camera_anchor` (`0x800242f0`) is the shared control opcode case `4/5` helper: one branch projects an opcode-provided point into the shared camera basis and seeds a fixed anchor through `DAT_80078a2c`, while the other branch clears that anchor and restores normal camera-follow behavior.
|
||||
- `psx_spawn_object_compound_effect_variant3` (`0x800184e8`) is the direct effect spawner used by control opcode case `8`: it builds a type-`2` compound record at the current object position, forces variant `3`, and triggers audio event `0x2c`.
|
||||
- `psx_flush_deferred_control_queue` (`0x8002aed0`) drains that queue once per world tick, applying each entry through `psx_apply_deferred_control_command`.
|
||||
- `psx_apply_deferred_control_command` fans one deferred entry out into both `psx_apply_deferred_control_to_dispatch_roots` and `psx_apply_deferred_control_to_live_objects`, which is the clearest current evidence that this queue is a small deferred world/control mutation lane rather than a render queue.
|
||||
- `psx_control_set_facing_direction` (`0x80024438`) is control opcode case `9`: it forces an explicit facing token and immediately refreshes the player or object movement-state around that heading.
|
||||
- The remaining neighboring helper at `0x800243b8` is still intentionally unnamed for now; it looks like a short delay gate around `psx_spawn_object_compound_effect_variant3`, but it still needs stronger subsystem evidence before it should get a behavioral name.
|
||||
- `psx_update_motion_and_nearby_interactions` is the broad world-motion and nearby-interaction integrator that sits between behavior updates and draw submission. For viewer purposes, this is the runtime bridge between authored map placement and the motion/state values that later feed heading-based state reselection and projection.
|
||||
- The cull-to-draw bridge is now closed too. `psx_authored_record_in_view_bounds` is the authored-record screen-space gate used by the two section-0 dispatch passes, while `psx_world_point_in_view_bounds` is the corresponding gate for already-instantiated live objects. Both use the same isometric camera basis around `DAT_800678d4`, which means the viewer can treat the record-family export as feeding the same projection space as the later live object list instead of as a separate map coordinate model.
|
||||
- The main world-object draw helper is now grounded more tightly as well. `FUN_80041458` builds the final sprite primitive from the authored screen rectangle at `obj+0x20..+0x2e`, then ORs in a palette override read from the original source-record pointer at `obj+0xa0`: for types `0x003e..0x00ab` it uses the high byte of source word `+0x06`, and for types `>= 0x00ac` it uses the high byte of source word `+0x0c`. That means the remaining viewer mismatch is not where the override comes from, but when the runtime chooses a different object/variant/state before draw.
|
||||
- The stage split is tighter too. `psx_project_object_special_visible_queue` (`0x80040f78`) feeds a distinct world-facing stage-2 queue, and `FUN_80041144` consumes that queue with the same projected screen rectangle fields and the same resource-specific draw helpers used by the stage-1 visible list. So the unreadable output is not explained by one missing HUD lane; the dominant gap is still the unresolved final art-binding path, with the stage-2 queue as a secondary world-object lane the viewer must eventually model.
|
||||
- The next high-value executable target is now partly closed. `FUN_8002906c` is now renamed `psx_type4_reselect_motion_state`, and the surrounding interaction cluster is finally concrete enough to describe instead of leaving it as a black box:
|
||||
- The next high-value executable target is now partly closed. `psx_type4_reselect_motion_state` (`0x8002906c`) is now named, and the surrounding interaction cluster is finally concrete enough to describe instead of leaving it as a black box:
|
||||
- `psx_type4_update_delayed_interaction` (`0x80029c20`) is the type-4-only delayed wrapper. It probes ahead, stores the hit object at the controller-local `+0x38` slot, seeds a countdown from distance and speed, and dispatches to `psx_type4_reselect_motion_state` when that delay matures.
|
||||
- `psx_type4_reselect_motion_state` (`0x8002906c`) is the post-construction reselection path for those delayed type-4 interactions. Depending on target flags it either hands off to the older `psx_object_reselect_state_from_target_vector` (`0x80028c94`) helper or flips the object's motion components against the target bounds, then reseats the live state script through `psx_object_select_state_script(obj, psx_object_quantize_motion_heading16(obj) >> 2 & 0xf)` before registering bilateral contact.
|
||||
- `psx_object_update_nearby_interactions` (`0x80029478`) is the broad nearby-object sweep that feeds most of the non-type-4 collision and interaction bookkeeping. It walks the active object set, culls locally, performs overlap checks, updates directional contact/block flags, and registers contact pairs.
|
||||
|
|
@ -838,9 +884,9 @@ Recovered per-level runtime-header lane:
|
|||
- The local post-advance render wrappers are also no longer anonymous labels:
|
||||
- `psx_spawn_compound_record_advance_state_once` (`0x80013618`) creates one compound-record object, forces its script countdown to `1`, immediately runs `psx_object_advance_state_script`, and then marks the object with `obj+0x1e |= 0x20`. This is the cleanest currently recovered example of a constructor wrapper that intentionally advances into a non-initial live state before the object joins the normal update/render flow.
|
||||
- `psx_spawn_simple_record_set_active_flag` (`0x8001372c`) is the simpler sibling for the simple-record constructor: create the object, then immediately set the low active flag in `obj+0x1e`.
|
||||
- `psx_object_refresh_main_visible_and_cleanup` (`0x80013688`) is the compact stage-1 handoff wrapper. When the object still has a drawable resource and the `0x20` flag is set, it feeds the object through `psx_project_object_main_visible`; if the object is not in the `obj+0x1c & 1` hold state but does carry `obj+0x1e & 0x10`, it then runs the usual `FUN_80027f80` cleanup/follow-up path.
|
||||
- `psx_object_refresh_main_visible_and_cleanup` (`0x80013688`) is the compact stage-1 handoff wrapper. When the object still has a drawable resource and the `0x20` flag is set, it feeds the object through `psx_project_object_main_visible`; if the object is not in the `obj+0x1c & 1` hold state but does carry `obj+0x1e & 0x10`, it then queues the object into the nearby-interaction active set through `psx_nearby_interaction_list_add`.
|
||||
- `psx_object_advance_state_and_queue_special_visible` (`0x80013758`) is the compact stage-2 handoff wrapper. If the object still has a drawable resource, it advances the active script and immediately queues the object through `psx_project_object_special_visible_queue`, then applies a small sentinel cleanup block that clears world coordinates and selected flags for specific type/selector cases.
|
||||
- The owner above those wrappers is now named too: `psx_object_integrate_motion_and_route_visible` (`0x800131a8`). It is the per-object bridge between movement state and rendering: it integrates position/velocity fields, refreshes the local visible/on-screen flags, handles the controlled-object side path, then advances the live state script and routes the object either into `psx_project_object_special_visible_queue` for the type-4/special-visible branch or into `psx_project_object_main_visible` for the normal drawable branch before the usual `FUN_80027f80` cleanup. This is the clearest recovered owner-level proof that state advancement and render-lane routing belong to the same runtime step.
|
||||
- The owner above those wrappers is now named too: `psx_object_integrate_motion_and_route_visible` (`0x800131a8`). It is the per-object bridge between movement state and rendering: it integrates position/velocity fields, refreshes the local visible/on-screen flags, handles the controlled-object side path, then advances the live state script and routes the object either into `psx_project_object_special_visible_queue` for the type-4/special-visible branch or into `psx_project_object_main_visible` for the normal drawable branch before the usual nearby-interaction enqueue step. This is the clearest recovered owner-level proof that state advancement and render-lane routing belong to the same runtime step.
|
||||
- Those wrappers matter because they close one more gap between `psx_object_advance_state_script` and the render split. The stage-1/stage-2 divergence is not only visible in larger caller bodies such as the `0x80013524` / `0x80013564` branches; it also exists as small dedicated wrappers that either project through the main visible list after state work or advance-and-queue directly into the special-visible pass. That makes the renderer problem look even less like a missing flat table and more like a true runtime pipeline with multiple post-script routing paths.
|
||||
- The render-side leaf chain is now close to end-to-end:
|
||||
- `psx_project_object_main_visible` and `psx_project_object_special_visible_queue` both use the current script word at `obj+0x94` as the frame selector they pass into the frame-metric helpers.
|
||||
|
|
@ -1042,6 +1088,8 @@ Per-bundle shipped inventory from the extracted disc tree:
|
|||
|
||||
### Passcodes and password-screen cheat status
|
||||
|
||||
Follow-up: the hidden passcode compare lane is now materially tighter in [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md). The recovered decoder at `0x8003ec8c` is table-driven rather than plain ASCII, and its special hidden rows now give the strongest current code-backed support for public PSX folklore that `L0SR` is the cheat-mode password candidate while `XXXX` routes into a separate hidden branch.
|
||||
|
||||
Current executable-backed passcode findings:
|
||||
|
||||
- The mission-complete passcode display path at `80022cd4` and `80022f1c` synthesizes a `4`-character code from generated indexes.
|
||||
|
|
@ -1131,7 +1179,9 @@ JL-2 / JL-9 follow-up:
|
|||
- neither `JL-2` nor `JL-9` appears in the known DOS `Weapon_GetNameForShapeNo` tables already extracted in this repo for retail Remorse or Regret; those tables stop at the older DOS weapon families such as `BA-40`, `BA-41`, `PA-21`, `EM-4`, `SG-A1`, `RP-22`, `RP-32`, `AR-7`, `GL-303`, `PA-31`, `PL-1`, `AC-88`, `UV-9`, and the Regret-only additions `BK-16`, `LNR-81`, `XP-5`
|
||||
- that makes `JL-2` and `JL-9` strong PSX-only naming additions rather than inherited PC names
|
||||
- `JL-2` is also the only one of the two with an explicit PSX ammo label (`JL-2 AMMO`) in the nearby executable text table, while no matching `JL-9 AMMO` string has been recovered
|
||||
- the extracted PSX `pickups_and_weapons` sprite category contains repeated weapon-pickup art across a large spread of maps, but this pass still does not have a defensible sprite-to-name mapping for specific `JL-2` or `JL-9` pickup appearances
|
||||
- the focused follow-up in [docs/psx/jl-9-investigation.md](docs/psx/jl-9-investigation.md) now closes one important part of the question: `JL-9` is a real executable-backed final weapon-definition row rather than just a stray PSX-only string, and the strongest current acquisition route is a hidden passcode -> debug gate -> bulk weapon unlock path that likely reaches the extra late weapon channel
|
||||
- that same focused follow-up now tightens the disambiguation further: the extra hidden-passcode/L0SR-adjacent non-PC weapon is now directly identified as `JL-9` (`0x0d`), while `JL-2` is the neighboring ordinary lane (`0x0c`)
|
||||
- exact sprite identity and exact level placement for `JL-9` remain open and still need runtime or asset-side correlation
|
||||
|
||||
### Enemies
|
||||
|
||||
|
|
@ -1153,6 +1203,6 @@ Current safest read:
|
|||
2. Recover the password-entry validation path directly so the hidden PSX cheat-password compare logic can be proven from code instead of only cross-referenced from public password lists.
|
||||
3. Focus map decoding on `post_audio_region_01` and `post_audio_region_02`, starting with table structures, coordinate ranges, and repeated record widths.
|
||||
4. Focus sprite/graphics decoding on `post_audio_region_04`, including more aggressive TIM validation and possible packed-image expansion.
|
||||
5. Recover the exact type IDs consumed by `level_resource_stream_load` so the sprite/image resource records can be labeled more precisely.
|
||||
5. Recover the exact type IDs consumed by `psx_stream_install_type_runtime_banks` so the sprite/image resource records can be labeled more precisely.
|
||||
6. Compare carved `post_audio_region_04` image assets against on-screen level graphics to separate sprite sheets from tiles.
|
||||
7. Run the raw-blob fallback across `MENUS/*.WDL` to identify which menu files contain usable embedded TIM data and which are likely packed 15-bit images.
|
||||
|
|
|
|||
|
|
@ -519,6 +519,58 @@ Features that are mostly just break-state/UI flags:
|
|||
- `single step` calls `usecode_debugger_break_state_enable_single_step`
|
||||
- source-file open, goto-line, search, and breakpoint-table editing are mainly source-buffer/UI features once the debugger is open
|
||||
|
||||
### 18. Retail Ghidra follow-up implied by the Regret mapping
|
||||
|
||||
The Regret pass is now strong enough to drive a specific retail `CRUSADER.EXE` naming batch rather than a generic `more seg109 cleanup` request.
|
||||
|
||||
The most important retail equivalents to promote explicitly in live Ghidra next are:
|
||||
|
||||
- `13a0:2882` = `usecode_debugger_build_menubar`
|
||||
- `13a0:088f` = `usecode_debugger_source_pane_create`
|
||||
- `13a0:0ae8` = `usecode_debugger_source_pane_init_view_from_break_state`
|
||||
- `13a0:0ba7` = `usecode_debugger_source_pane_handle_command`
|
||||
- `13a0:0f16` = `usecode_debugger_source_pane_handle_pointer_event`
|
||||
- `13a0:1088` = `usecode_debugger_source_line_copy_for_display`
|
||||
- `13a0:1118` = `usecode_debugger_source_pane_draw_visible_lines`
|
||||
- `13a0:1413` = `usecode_debugger_source_pane_clamp_viewport`
|
||||
- `13a0:15ac` = `usecode_debugger_source_pane_load_file`
|
||||
- `13a0:16ee` = `usecode_debugger_watch_pane_create`
|
||||
- `13a0:1791` = `usecode_debugger_watch_pane_draw`
|
||||
- `13a0:193f` = `usecode_debugger_watch_pane_handle_click`
|
||||
- `13a0:1c2c` = `usecode_debugger_translate_registered_event`
|
||||
- `13a0:1dc6` = `usecode_debugger_forward_child_event`
|
||||
- `13a0:2c2e` = `usecode_debugger_source_buffer_create_from_path`
|
||||
- `13a0:2ca0` = `usecode_debugger_source_buffer_destroy`
|
||||
- `13a0:2d14` = `usecode_debugger_source_buffer_open_from_path`
|
||||
- `13a0:2e0a` = `usecode_debugger_source_buffer_load_text`
|
||||
- `13a0:2f4f` = `usecode_debugger_source_buffer_split_lines_in_place`
|
||||
- `13a0:301d` = `usecode_debugger_source_buffer_get_line_ptr`
|
||||
|
||||
Why this follows from the Regret result rather than from guesswork:
|
||||
|
||||
- the retail `000b:* -> 13a0:*` table already closes the same UI layer at the raw/reference level
|
||||
- the Regret `1398:*` cleanup shows the same function ordering and subsystem boundaries in a build where the debugger bootstrap survived
|
||||
- that makes the remaining retail seg109 backlog primarily a promotion/documentation task, not a fresh discovery task
|
||||
|
||||
The live retail rename pass also closed one useful correction: the original retail-first mirror list over-assigned `13a0:1791` and `13a0:193f` to the source pane. Current decompile evidence now makes the split cleaner: `13a0:16ee/1791/193f` form a watch-pane lane, while the source-pane lane retains the file-load, pointer-event, line-copy, and viewport helpers.
|
||||
|
||||
This is the right pre-patch documentation step because it turns the surviving retail debugger lane into named UI, event, and source-buffer surfaces instead of leaving the future patch window hidden among anonymous helpers.
|
||||
|
||||
### 19. Delivery implication after the patching stall
|
||||
|
||||
The Regret result changes what `practical next step` should mean.
|
||||
|
||||
The next useful proof is no longer `try another manual hex patch`. It is one of these two:
|
||||
|
||||
1. a runtime-only memory experiment that proves the create/store/open model on a clean executable
|
||||
2. a reproducible scripted patch against a writable clone, driven by a verified byte plan rather than by hand edits
|
||||
|
||||
Why the Regret comparison pushes in that direction:
|
||||
|
||||
- it already tells us the missing retail behavior is a small bootstrap-and-vtable problem, not a whole missing subsystem
|
||||
- it shows that the real stability question is whether live interpreter state is seeded correctly, not whether the source pane exists
|
||||
- it therefore favors runtime proof and scripted reproducibility over one-off static patching attempts
|
||||
|
||||
Features that clearly depend on seeded current-entry runtime payload:
|
||||
|
||||
- `Inspect what?`
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ Families that are only `callback-shaped` or `object-like` but still lack a safe
|
|||
| `WatchEntityController` | Medium | Global controller/watch/camera object with explicit virtual dispatch and startup/display involvement | global object at `0x2bd8`, dispatch wrapper, create-global path, startup/display callsites | `0007:ba00 watch_entity_controller_create_global` delegates to `0007:ba45 watch_entity_controller_create`, stamps type `0x2c2b`, stores global object | no direct destructor identified in current notes | repeated dispatch through vtable slots `+0x24`, `+0x2c`, and `+0x30` | global-object ownership clearer than field layout; seed row at `0x2be4` into callback table | Worth inventorying now because it will benefit immediately from namespace/method grouping |
|
||||
| `EntityVmRuntime` | High | Main VM runtime object that owns owner-resource helper, cached slot/value state, and category-base setup | creation/load path is structurally stable and repeatedly cross-checked against extracted usecode evidence | `000d:44df entity_vm_runtime_init_from_path_if_configured`; `000d:4c99 entity_vm_runtime_create` | destroy path not fully named in the snippets here, but owner-resource destroy is known and runtime state/save-load consumers are well constrained | not a classic gameplay vtable family in the current notes, but method-style ownership and object fields are stable | object size and field zones strongly implied by `+0x10c/+0x10e`, `+0x117/+0x119`, `+0x1315/+0x1317` and related runtime state | Major lift target because VM readability is a blocker for recompilable source |
|
||||
| `EntityVmOwnerResource` | High | File-backed helper owned by VM runtime that indexes source tables and materializes owner rows | helper object shape and per-entry loader contract are already tight | `000d:7000 entity_vm_runtime_owner_resource_create` allocates helper/object tables | paired destroy helper `000d:70fd entity_vm_runtime_owner_resource_destroy` is documented in related notes | helper method-table uses slots `+0x04` size-query and `+0x0c` materialization callback | helper-owned count `+0x14`, far-pointer table `+0x10`, paired word table `+0x18`, owner rows stride `0x0d` | One of the cleanest non-gameplay object families for typed struct work |
|
||||
| `NPCActionProcess` family | High | Bounded NPC AI process family with a shared base shell and derived stand/pace/surrender/guard/loiter policies | live process-table ownership now grounds the shared slot-1 destroy path, shared slot-10 no-op, and the bounded per-family create/run/destroy entries in seg033 | `1100:0000 NPCActionProcess_Create`, `1100:02ed StandProcess_CreateProcess`, `1100:0383 SurrenderProcess_CreateProcess`, `1100:0693 PaceProcess_CreateProcess`, `1100:0984 GuardProcess_CreateProcess`, `1100:0afb LoiterProcess_CreateProcess` | `1100:1089 NPCActionProcess_Destroy`, `1100:1036 StandProcess_Destroy`, `1100:0437 SurrenderProcess_Destroy`, `1100:0fe8 PaceProcess_Destroy`, `1100:0f95 GuardProcess_Destroy`, `1100:0f47 LoiterProcess_Destroy` | shared slot-10 base no-op at `1100:0fe3` plus loiter-only slot-10 override at `1100:0d3e`; broader slot semantics still open | current layout evidence is still thin, but `SurrenderProcess_Destroy` already proves family-local state plus two embedded dispatch-entry children | High navigation value for gameplay/NPC AI work even before a safe datatype pass |
|
||||
| `EntityVmContext` | Medium | Per-slot/per-entity VM context object built from runtime and owner-resource data | create/setup/load helpers already have clear ownership, but broader dispatch semantics are still active work | `000d:46ec entity_vm_context_create_from_slot_index` and related masked-create wrappers | no single destroy method is highlighted in the current note set used here | context-side dispatch and busy-state updates through virtual or callback-like method surface at least on the context object | stable fields include `+0x32/+0x34`, `+0xd6/+0xd8`, `+0x102`, `+0x10c/+0x10e`, `+0x11b/+0x11d`, `+0x123` | Important for VM readability, but should follow runtime and owner-resource typing |
|
||||
| `CacheBackendObject` | Medium | Small backend/cache loader object with DOS file-handle state and method table | constructor and callback roles are already explicit | `0009:5600 cache_backend_object_init` allocates `0x20` bytes and seeds method-table state | no explicit destructor named in current note slice | backend callback roles at `+0x34` and `+0x0c` are verified in cache lookup/load path | concrete `0x20`-byte size; fields at `+0x08`, `+0x0c`, `+0x10`, `+0x14`, `+0x16`, `+0x18`, `+0x1c` | Good contained family for early datatype work |
|
||||
| `PresentationCallbackBroker` | Low | Video/presentation-state callback broker rooted at `0x4588` | init/teardown/callback slot evidence is real, but subsystem naming remains intentionally conservative | `runtime_callback_object_init_once` family is documented, but not all constructor details are fully promoted here | `runtime_callback_object_teardown_once` and finalize path are explicit | vtable slots `+0x04`, `+0x08`, `+0x0c` all have live evidence | global state at `0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6`; payload fields from caller objects at `+0x12d/+0x12f`, `+0x74f/+0x751` | Useful as a typed broker object later, but not a good first namespace/class pilot |
|
||||
|
|
@ -50,13 +51,14 @@ If the goal is to make later class-authoring work fast and low-risk, the best or
|
|||
1. `EntityDispatchEntryBase`
|
||||
2. `EntityDispatchEntryRuntimeState`
|
||||
3. `SpriteNode`
|
||||
4. `EntityVmOwnerResource`
|
||||
5. `CacheBackendObject`
|
||||
6. `WatchEntityController`
|
||||
7. `Entity`
|
||||
8. `EntityVmRuntime`
|
||||
9. `EntityVmContext`
|
||||
10. `PresentationCallbackBroker`
|
||||
4. `NPCActionProcess` family
|
||||
5. `EntityVmOwnerResource`
|
||||
6. `CacheBackendObject`
|
||||
7. `WatchEntityController`
|
||||
8. `Entity`
|
||||
9. `EntityVmRuntime`
|
||||
10. `EntityVmContext`
|
||||
11. `PresentationCallbackBroker`
|
||||
|
||||
This order prioritizes bounded families with visible constructors, derived variants, or explicit method tables before the larger gameplay and VM surfaces.
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ That set gives the high-level target, the current candidate families, the rebuil
|
|||
- [docs/sprite-node-class-layout.md](docs/sprite-node-class-layout.md): `SpriteNode` destructor/event surface and candidate virtual-slot map.
|
||||
- [docs/entity-class-family-split.md](docs/entity-class-family-split.md): conservative split of the large `Entity` lane into base, projectile, debris, corpse/actor, and adjacent non-entity families.
|
||||
- [docs/entity-vm-runtime-owner-resource-layout.md](docs/entity-vm-runtime-owner-resource-layout.md): current runtime/helper/context ownership model for the VM lane.
|
||||
- [docs/npc-action-process-class-layout.md](docs/npc-action-process-class-layout.md): current owner-first class-lift state for the bounded seg033 NPC AI process family.
|
||||
- [docs/presentation-callback-broker-layout.md](docs/presentation-callback-broker-layout.md): current object/lifecycle/vtable evidence for the `0x4588` presentation-state callback broker family.
|
||||
- [docs/usecode-debugger-break-state-layout.md](docs/usecode-debugger-break-state-layout.md): current object/lifecycle/layout evidence for the dormant seg1408 debugger-state family.
|
||||
|
||||
|
|
@ -134,9 +135,17 @@ Current authored `Remorse` classes in the active database are:
|
|||
- `EntityVmRuntime`
|
||||
- `EntityVmContext`
|
||||
- `EntityVmSlotEntry`
|
||||
- `NPCActionProcess`
|
||||
- `StandProcess`
|
||||
- `PaceProcess`
|
||||
- `SurrenderProcess`
|
||||
- `GuardProcess`
|
||||
- `LoiterProcess`
|
||||
|
||||
The VM lane is still the furthest along in actual Ghidra authoring. Recent live batches added the bounded `EntityVmSlotEntry` class owner plus more owned `EntityVmRuntime` methods (`GetSlotChunkPtrAtOffset`, `ReleaseSlotChunkRef`, `TryUnloadSlotChunk`, `DebugDumpSlotMemory`, `ApplyToMatchingOwnerRows`) rather than stopping at free-function naming.
|
||||
|
||||
The new bounded NPC-family batch is intentionally lighter on datatypes than the VM and dispatch-entry work, but it is still real class lifting rather than mere renaming. The live database now has owner-first class shells for the seg033 AI-process family with `NPCActionProcess` as the shared base owner and `StandProcess`, `PaceProcess`, `SurrenderProcess`, `GuardProcess`, and `LoiterProcess` as derived behavior owners. The safest current stop point is still owner-first only: shared create/destroy/no-op virtual entries and the direct per-family create/run/destroy methods are lifted, while datatype and slot-order work remain open until the process-state layout and vtable roots are tighter.
|
||||
|
||||
The next planned pilot family is no longer purely preparatory either. `Remorse::EntityDispatchEntry` now exists as a real class owner in-session with a first provisional `/Remorse/EntityDispatchEntryBase` datatype covering the stable field block through `+0x18` and a matching `/Remorse/EntityDispatchEntryVtable` datatype exposing only the verified `+0x14` and `+0x28` callback slots. The first base-method batch has also landed from the old `0008:` note cluster after re-anchoring that range onto the live `11e0:` process-substrate segment: `InitBase`, `SetSourceType`, `SetEventTypeChecked`, `SetGroupId`, `Unlink`, and `IncrementGroupId` now live under the class owner with provenance comments preserved.
|
||||
|
||||
That family also has its first derived slice now. The old `000d:7e00/8078` runtime-state pair is re-anchored in the live `1440:` fade/palette cluster as `InitRuntimeState` and `ReleaseRuntimeState`, and `/Remorse/EntityDispatchEntryRuntimeState` now exists as a provisional overlay datatype with the recovered `+0x40..+0x4c` runtime-state tail fields. That is a meaningful pause point because the pilot family now has a class owner, a base datatype, a vtable shell, a first base-method batch, and one concrete derived/runtime-state batch rather than just one isolated constructor lane.
|
||||
|
|
|
|||
|
|
@ -141,6 +141,80 @@ Implication:
|
|||
- but only after the debugger gump already exists and is registered
|
||||
- this does **not** give us a new no-patch retail entry path by itself
|
||||
|
||||
### 5. Retail still preserves a substantial debugger UI and command surface once the gump exists
|
||||
|
||||
The latest live `CRUSADER.EXE` decompile pass makes the surviving retail debugger capability map much clearer.
|
||||
|
||||
What retail can still do once a valid debugger object and gump already exist:
|
||||
|
||||
- `usecode_debugger_build_menubar` still builds the full hidden debugger menu bar with File, Run, Breakpoints, Search, and Data menus.
|
||||
- `usecode_debugger_translate_registered_event` still translates the registered debugger/control event bundle into the local debugger command ids consumed by `usecode_debugger_handle_event`.
|
||||
- `usecode_debugger_handle_event` still implements real debugger actions rather than decorative UI stubs: open file, run, break-next, single-step, go to line, watch, inspect, change global, search, search again, break to TDP, and watch clearing.
|
||||
- `usecode_debugger_source_pane_draw_visible_lines` still clamps the source viewport, highlights the current line, scans the breakpoint table for visible marks, and draws the loaded source rows from the `.unk` buffer.
|
||||
- `usecode_debugger_source_pane_handle_pointer_event` still converts pointer position into source line and column state and also drives scroll-style commands when the pointer is above or below the pane.
|
||||
- the child-pane split is now clearer in retail than in the earlier first-pass mirror list: one pane is the loaded source view, while the sibling `13a0:16ee/1791/193f` lane is a separate watch-pane create/draw/click surface.
|
||||
|
||||
The live retail file-loading path is now also much clearer than before.
|
||||
|
||||
- `usecode_debugger_source_pane_load_file` resets local source-pane cursor state, destroys any previous source buffer, allocates a fresh source-buffer object from the requested path, updates the pane's line-count field, and refreshes the child widgets.
|
||||
- `usecode_debugger_source_buffer_create_from_path` and `usecode_debugger_source_buffer_open_from_path` still build a dedicated far-memory file object, normalize the requested path, open the file, and pass it into the text-loader path.
|
||||
- `usecode_debugger_source_buffer_load_text` still reads the whole file into far memory, rejects obviously non-text inputs, and hands the buffer to `usecode_debugger_source_buffer_split_lines_in_place`.
|
||||
- `usecode_debugger_source_buffer_split_lines_in_place` still walks the loaded text, zero-terminates newline boundaries in place, and populates the per-line pointer table later consumed by draw, click, search, and goto-line logic.
|
||||
- `usecode_debugger_source_buffer_get_line_ptr` is still the shared accessor used by source draw, pointer handling, find/find-next, and source-pane command handling.
|
||||
|
||||
Retail source navigation helpers are now closed well enough to describe the pane behavior directly.
|
||||
|
||||
- `usecode_debugger_set_line_selection` clamps the requested line against the loaded file range, clears transient cursor state, optionally updates the anchor line, and forces a redraw.
|
||||
- `usecode_debugger_center_on_line` stores the target current line, computes a top-of-window line from the visible row count, and then delegates to `usecode_debugger_set_line_selection`.
|
||||
- this means retail still preserves the ordinary source-browser workflow expected from the strings: load file, jump to line, center on current line, search through lines, and redraw the viewport around the active selection.
|
||||
|
||||
The watch-pane side is also more concrete now.
|
||||
|
||||
- `usecode_debugger_watch_pane_create` still allocates a real watch child gump and installs the watch-pane vtable.
|
||||
- `usecode_debugger_watch_pane_draw` still iterates the shared 10-slot watch table at `1478:5580`, asks the break-state callback table to format each populated watch entry, and highlights the selected row.
|
||||
- `usecode_debugger_watch_pane_handle_click` still converts pointer Y position into a watch-row index, updates the selected watch slot, and triggers a repaint.
|
||||
- that is stronger evidence for `interactive watch list still survives` than the earlier weaker `watch-related strings still exist` wording.
|
||||
|
||||
Current safest interpretation:
|
||||
|
||||
- retail did not merely keep a few string tables or unused menu labels
|
||||
- retail still keeps a functioning debugger front-end shell with real search, breakpoint, watch, inspect, and source-view logic
|
||||
- the main missing piece is still how execution ever reaches that shell with valid live state
|
||||
|
||||
### 6. Retail callback and reachability limits are still the decisive difference from Regret
|
||||
|
||||
The same live pass also tightened the `what retail still cannot do` side.
|
||||
|
||||
Current direct-caller state in live `CRUSADER.EXE`:
|
||||
|
||||
- `usecode_debugger_open_for_current_unit` still has no recovered callers
|
||||
- `usecode_debugger_open_modal` still has no recovered callers
|
||||
- `Remorse::UsecodeDebuggerBreakState::Create` at `1408:0000` still has no recovered callers
|
||||
- `usecode_debugger_handle_event` is still only reached through `usecode_debugger_translate_registered_event` and `usecode_debugger_forward_child_event`
|
||||
|
||||
Current callback state is still the key retail blocker too:
|
||||
|
||||
- `Remorse::UsecodeDebuggerBreakState::OnBreakTriggeredNoop` at `1408:046f` is still the live slot-0 callback
|
||||
- `Remorse::UsecodeDebuggerBreakState::VtableSlot1ReturnZero` at `1408:0474` is still the live slot-1 callback
|
||||
- unlike Regret, retail still has no recovered bootstrap that rewires the break-state object onto a live frontend-aware vtable
|
||||
|
||||
So the retail-versus-Regret split is now even sharper:
|
||||
|
||||
- both builds preserve a large debugger UI/event subsystem
|
||||
- retail still lacks the recovered object bootstrap and live callback target that would naturally open the debugger on break
|
||||
- Regret keeps both the writer/bootstrap path and the vtable upgrade that turns break-state callback slot `0` into a real `open_for_current_unit` launch path
|
||||
|
||||
That is why retail still reads as `functional debugger shell plus dormant break-state object`, while Regret reads as `same shell plus a surviving end-to-end open-on-break path`.
|
||||
|
||||
One practical refinement from the latest retail pass is that the shell is not merely a static menu/window skeleton.
|
||||
|
||||
- the file-open path still reaches a real far-memory source-buffer loader
|
||||
- the search path still walks live source lines through `usecode_debugger_source_buffer_get_line_ptr`
|
||||
- the goto-line path still updates line selection through the same source-pane helpers used by current-line centering
|
||||
- the watch path still stores, formats, selects, clears, and redraws real watch rows
|
||||
|
||||
So the remaining barrier is still entry/bootstrap, not lack of interior debugger behavior after entry.
|
||||
|
||||
## What This Means For Usecode As An Entry Path
|
||||
|
||||
## Current Best Read
|
||||
|
|
@ -261,13 +335,112 @@ Why this is now preferred over more retail patch fishing:
|
|||
- if No Regret or JP No Remorse kept any surviving debugger bootstrap, it could collapse the retail problem from `invent a new path` to `port or mimic one missing write/call`
|
||||
- that is more likely to produce a truly minimal modification than another speculative retail patch chain
|
||||
|
||||
## Retail Ghidra Naming Backlog
|
||||
|
||||
The current note corpus now supports a tighter retail seg109 naming batch than the live authoring summary currently reflects.
|
||||
|
||||
These are the most important retail debugger-side helpers to promote explicitly in the active `CRUSADER.EXE` database before any new patch design work:
|
||||
|
||||
- `13a0:2882` = `usecode_debugger_build_menubar`
|
||||
- `13a0:088f` = `usecode_debugger_source_pane_create`
|
||||
- `13a0:0ae8` = `usecode_debugger_source_pane_init_view_from_break_state`
|
||||
- `13a0:0ba7` = `usecode_debugger_source_pane_handle_command`
|
||||
- `13a0:0f16` = `usecode_debugger_source_pane_handle_pointer_event`
|
||||
- `13a0:1088` = `usecode_debugger_source_line_copy_for_display`
|
||||
- `13a0:1118` = `usecode_debugger_source_pane_draw_visible_lines`
|
||||
- `13a0:1413` = `usecode_debugger_source_pane_clamp_viewport`
|
||||
- `13a0:15ac` = `usecode_debugger_source_pane_load_file`
|
||||
- `13a0:16ee` = `usecode_debugger_watch_pane_create`
|
||||
- `13a0:1791` = `usecode_debugger_watch_pane_draw`
|
||||
- `13a0:193f` = `usecode_debugger_watch_pane_handle_click`
|
||||
- `13a0:1c2c` = `usecode_debugger_translate_registered_event`
|
||||
- `13a0:1dc6` = `usecode_debugger_forward_child_event`
|
||||
- `13a0:2c2e` = `usecode_debugger_source_buffer_create_from_path`
|
||||
- `13a0:2ca0` = `usecode_debugger_source_buffer_destroy`
|
||||
- `13a0:2d14` = `usecode_debugger_source_buffer_open_from_path`
|
||||
- `13a0:2e0a` = `usecode_debugger_source_buffer_load_text`
|
||||
- `13a0:2f4f` = `usecode_debugger_source_buffer_split_lines_in_place`
|
||||
- `13a0:301d` = `usecode_debugger_source_buffer_get_line_ptr`
|
||||
|
||||
Why this batch matters before more patching:
|
||||
|
||||
- it converts the remaining patch-target area from anonymous `FUN_13a0_xxxx` bodies into named UI, event, and source-buffer lanes
|
||||
- it reduces the chance of patching the wrong helper when the debugger gump is already on-screen but still miswired
|
||||
- it makes runtime-only experiments easier to reason about because the gump lifecycle, source loading, and event forwarding chain become legible in-session
|
||||
|
||||
The current strongest provenance for this retail batch is the combined retail `000b:* -> 13a0:*` table in `ne-segment1.md` plus the one-to-one structural match against the now-closed Regret `1398:*` family.
|
||||
|
||||
One live correction from the follow-up rename pass matters here: retail `13a0:16ee/1791/193f` reads as a watch-pane constructor/draw/click trio, while the source-pane lane remains centered on `13a0:088f/0ae8/0ba7/0f16/1088/1118/1413/15ac`. So the Regret-side structural match is still valuable, but the retail child-pane split is now sharper than the earlier first-pass list implied.
|
||||
|
||||
## Practical Alternatives To Manual Hex Patching
|
||||
|
||||
The current blocker is no longer `we do not know what to patch`. It is `the delivery path needs to stop depending on blind byte edits`.
|
||||
|
||||
### 1. Runtime-only proof via DOSBox-X debugger or equivalent live memory tooling
|
||||
|
||||
This is now the cleanest first confirmation path.
|
||||
|
||||
Use it to:
|
||||
|
||||
- keep the retail executable on disk unchanged
|
||||
- patch or seed the debugger object only in live memory
|
||||
- prove whether a create/store/open sequence is sufficient before committing to any permanent binary patch
|
||||
|
||||
What this should target first:
|
||||
|
||||
- seeding `1478:659c/659e` with a valid debugger-state object
|
||||
- reusing the existing interpreter callback lane at `1418:049e..04b5`
|
||||
- testing whether `13a0:020d` or the vtable callback path can open a stable debugger gump once state exists
|
||||
|
||||
This is especially attractive because it turns the current question from `did we edit the NE file correctly?` into `does the runtime model itself actually work?`
|
||||
|
||||
### 2. Scripted patch application to a writable clone, not manual hex editing
|
||||
|
||||
If a permanent retail patch is still wanted, the next step should be a reproducible patcher, not another manual byte-edit round.
|
||||
|
||||
That means:
|
||||
|
||||
- keep a dedicated writable clone of the executable
|
||||
- store each patch as `address + expected old bytes + new bytes + reason`
|
||||
- apply it through a scriptable patcher that validates the original bytes before writing
|
||||
- regenerate the same patch on demand instead of hand-transcribing offsets each time
|
||||
|
||||
This can be done with PowerShell or Python against raw file offsets even if Ghidra export remains unreliable.
|
||||
|
||||
### 3. Use Ghidra only for analysis and verified byte plans
|
||||
|
||||
The current evidence does not support treating Ghidra export as the final patch-delivery mechanism for this lane.
|
||||
|
||||
What still works well:
|
||||
|
||||
- identify the correct callsite and byte budget in Ghidra
|
||||
- annotate the patch rationale in-session
|
||||
- test the control-flow hypothesis on a writable target
|
||||
- then convert the verified result into an external patch manifest or launcher-side patcher
|
||||
|
||||
That keeps Ghidra in the role it is good at here: reverse-engineering and patch design, not blind final-binary distribution.
|
||||
|
||||
### 4. Keep `-u` as the low-risk data-side experiment surface
|
||||
|
||||
The `-u` override still does not solve the missing bootstrap, but it remains valuable for adjacent experiments that do not require byte writes.
|
||||
|
||||
Use it for:
|
||||
|
||||
- testing whether scripted monitor/camera/control families can get closer to debugger-adjacent compiled paths
|
||||
- validating source-file and unit-name assumptions without touching the executable
|
||||
- separating `data-side idea failed` from `patching workflow failed`
|
||||
|
||||
It should stay in the toolbox, but it should not be mistaken for a direct replacement for the missing retail bootstrap.
|
||||
|
||||
## Current Recommendation
|
||||
|
||||
If the goal is the minimum modification that still has a realistic chance to work, the order should now be:
|
||||
|
||||
1. Compare `REGRET.EXE` and JP `/ja/CRUSADER.EXE` for any surviving debugger bootstrap/writer.
|
||||
2. Keep `-u` / replacement `EUSECODE.FLX` as the preferred low-risk experiment surface for any script-side proxy ideas.
|
||||
3. Do **not** resume broader retail executable patching unless the cross-build pass fails to yield a clearer bootstrap or the existing O/P family gets one clean runtime confirmation target.
|
||||
1. Promote the retail seg109 naming backlog above so the remaining debugger lanes are explicit in Ghidra.
|
||||
2. Use runtime-only memory seeding on a clean executable to prove or kill the bootstrap theory without committing file changes.
|
||||
3. Compare `REGRET.EXE` and JP `/ja/CRUSADER.EXE` for any surviving debugger bootstrap/writer that can replace a custom retail bootstrap.
|
||||
4. Keep `-u` / replacement `EUSECODE.FLX` as the preferred low-risk experiment surface for any script-side proxy ideas.
|
||||
5. Do **not** resume broader retail executable patching unless the runtime proof or cross-build pass yields one clear small patch plan that can be applied by script to a writable clone.
|
||||
|
||||
That ranking fits both the new live evidence and the user's practical constraint that complex retail patch attempts have already been unstable.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue