Crusader_Decomp/docs/psx/map-storage-model.md
2026-04-12 14:45:08 +02:00

36 KiB

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:

  • 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

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