778 lines
36 KiB
Markdown
778 lines
36 KiB
Markdown
|
|
# 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
|