32 KiB
32 KiB
PSX Map Viewer And JL-9 Investigation Plan
Scope
- Active target: retail PlayStation
SLUS_002.68already loaded in Ghidra. - Keep all PSX documentation in
docs/psx/. - Primary objective: get PSX maps loading into the existing map viewer coherently.
- Secondary objective: make PSX graphics export with the correct palette automatically instead of by partial heuristics.
- Tertiary objective: determine whether
JL-9is a real weapon in the PSX build, how it is unlocked or granted, and which sprite/bundle represents it.
Current State
- The detailed architectural write-up now lives in
docs/psx/map-rendering.md; keep that file as the long-form reference for storage format, runtime banks, render lanes, and viewer reassembly, and keep this file focused on the active plan and remaining blockers. docs/psx/psx.mdalready closes the boot executable, the broadLSET*.WDLlayout, and the likely split between map-like regions and graphics-like regions.- The earlier
region00-firstviewer export is now known to be based on a bad assumption: the~45..59records it exposes per map are only the small top-level WDL descriptor stream, not the full level content. - The stronger current model is a multi-section bundle layout: a top-level
0x18-byte dispatch-record table, typed subordinate resource tables rooted atDAT_800758cc/d0/d4/d8, and at least one separate compressed level-state blob that is inflated intoDAT_8006769cbyFUN_8003b00c(..., 0x3e00, 0x3e00). - The strongest current graphics source remains
post_audio_region_04. - A first PSX debug scene has already been exported experimentally, but the active workflow is now the renderer-local
.cachepipeline rather thansiteoutput. - The active live probe now builds provisional real-art atlases in
map_renderer/src/build-psx-cache.jsfrommap_renderer/STATIC_PSXinto.cache/psx,.cache/reference-data/psx-remorse, and.cache/scene-cache/psx-remorse/.... - The current verified processed build exposes
62PSX maps in the live renderer catalog under the runtime-record scene format (4032atlas-backed shapes,1925packed shared atlases after the latest atlas pass). - The exporter root cause is now clearer: the old five-region post-audio carve was still masking the real visible payload. Loader-sized
post_audio_section_00contains both the small0x18root descriptor rows and the dense 24-byte bulk placement rows, so the cache builder now recovers both visible families from that first real section instead of from the guessedregion00/region01split. - A verified full rebuild now carries
region00 + region01across all62maps.LSET1/L0.WDLnow emits1189items,LSET1/L1.WDLemits754, and every rebuilt map now reportsuniqueZCount > 1instead of the earlier mostly-flatz = 0export. - The next subordinate layers are now structurally split too:
DAT_800758d8is the per-type art/template bank,DAT_800758d0feeds the simple constructor's local component payload, andDAT_800758cc/d4feed the compound constructor's state/variant tables. The renderer-side serialization gap is now closed too: the currentpsx-runtime-record-probe-v6path exports those banks intostateLayers, and the scene writer preserves them in both scene metadata andmapSource. - The live viewer now trims the heavyweight
stateLayersanddecodedRuntimeLayersblobs back out of the interactive runtime scene after load and only reattaches them for scene-JSON export. That keeps the executable-backed research payload reversible without forcing normal pan/zoom interaction to carry the full PSX bank dumps. - The compound-bank finder is broader now too. When a typed-section-16 bank is not found at a parsed section boundary, the cache builder falls back to an absolute-file scan, which is how the late
DAT_800758cc/d0/d4source candidates now land in the exported scene state. - The late LSET template bank is now less speculative too. The currently working map-local
DAT_800758d8candidate is not the old "small typed section" guess; on retailLSET1/L9.WDLit decodes cleanly only when the parser treats the late large section as a bank with an embedded+0x38start, which is now enough to recover real bundle-backed mappings for a first subset of map types. - The main visible bulk layer is no longer flat. The accepted
region01placements now use the constructor-backed+0x06byte as provisionalz, andLSET1/L0.WDLcurrently exports11distinct structured elevation levels instead of one forcedz = 0plane. - One renderer-side mismatch is now closed: PSX sprites use authored
item.screenrectangles, and the bounding/highlight overlay path now uses those same authored rectangles instead of recomputing a DOS-style wireframe from provisionalworldcoordinates. - The executable now closes the last projection stage: authored object coordinates land in object fields
+0x3c/+0x40/+0x44as16.16fixed-point values, andFUN_80040d44/FUN_80040f78project them withscreen_x = y - xandscreen_y = 2*z - (x + y)/2before 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.
- The scene/cache naming now uses executable-backed family names (
section0_dispatch_roots,section0_constructor_placements) with the oldregion00/region01labels kept only as legacy aliases. - The offline
FUN_8003b00cpath now exists in the renderer-local exporter and serializes one candidate on-disk compressed source plus the decoded0x3e00state 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
9moved from0resolved bundle-mapped items to111after the template pass switched to the embedded late-section parse, even though unresolved root-dispatch families such as0x0042and0x0049still 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.
- A focused art-binding recovery pass is now landed in the cache builder too; see
docs/psx/art-binding-recovery.mdfor the measured recovery note. The exporter now treats many zero-blockDAT_800758d8constructor-placement types as inherited-art candidates instead of dropping directly to placeholders, first via same-mapDAT_800758ccscript-signature donors and then via a constrained nearest-donor fallback inside the current0x003e..0x0064constructor-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,262fallback items /1,714bundle-mapped items down to25,038fallback items /34,938bundle-mapped items. Representative maps such as0,9, and43are now mostly real-art scenes, whilemap 104remains the clear outlier with866fallback items versus only136bundle-mapped items. - The old fallback art binding is now positively disproven for map rendering, not just "still unverified": in the live cache, early
section0_dispatch_rootstypes0x0042and0x0049repeatedly bind to portrait/talk-animation bundles (for example map0offsets0x000B2970and0x000D84F4), 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_recordandpsx_object_create_compound_recordboth index the same per-type banks rooted atDAT_800758d8/d0/cc/d4;psx_object_select_state_scriptselects an active state script fromDAT_800758cc,psx_object_advance_state_scriptat0x80025d68interprets sentinel-driven script records,psx_object_lookup_variant_entryresolves a companion entry fromDAT_800758d4, andpsx_reset_type_runtime_banks_fromat0x80025ce8is the nearby bank-reset helper that had been misnamed earlier. So the missing map-render rule is not one flattype -> bundletable but a multi-stage runtime selection path. - The visible render pass is less opaque now too.
FUN_80041378draws in three stages: the sorted visible-object list throughFUN_80041458, a second special-visible list throughFUN_80041144, and then HUD/overlay/icon primitives throughFUN_800416cc. That means the remaining map-viewer gap is still mainly in world-object and special-object families, not in the HUD pass. - The next decompilation target is narrower now too:
FUN_8002906cis the highest-value follow-up because it is a verified post-construction state reselection path that can overwrite the live script choice fromFUN_8003bc1c(obj) >> 2 & 0xf, which is exactly the missing bridge between exported placement selectors and the art-facingDAT_800758d4variant lookup. - The stage-2 path is now strong enough to affect renderer planning directly.
FUN_80040f78is the queue-builder for theFUN_80041144pass: it projects an object just like the mainFUN_80040d44path but appends it toDAT_80078b70/DAT_80067472instead of the mainDAT_8006ad5cvisible 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_loopis the outer level-session loop;wdl_resource_bundle_load_by_indexperforms the actualSPEC_A.WDL+ selectedLSET*.WDLload and root-record dispatch;psx_world_frame_tickis the normal per-frame world loop; andFUN_80041378is 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_rootsdispatches theDAT_800677200x18-stride family plus the extra fixed-size entries nearDAT_80067658, whilepsx_dispatch_section0_constructor_placementsdispatches theDAT_800678f00x0c-stride family. Those are the closest executable matches for the currentsection0_dispatch_rootsandsection0_constructor_placementsviewer families. - The live-object passes are separated too:
psx_run_live_object_type_updatesruns the per-type update callback over the linked live object list atDAT_800675ac,psx_run_live_object_behavior_callbacksruns the later per-object behavior callback stored on each object, andFUN_80029de0is the broad world/player motion integrator that sits between behavior updates and draw submission. - The cull/draw bridge is now explicit too:
FUN_800423b0gates the two authored record-family dispatch passes,FUN_80042424gates already-instantiated live objects, andFUN_80041458draws from the final authored screen rectangle while sourcing palette overrides directly from the original record pointer atobj+0xa0. - Palette override provenance is tighter too: object field
+0xa0is the original authored source-record pointer written by both constructors, so the current override path inFUN_80041458is 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
0x0050selector map (0..3 -> frame 0..3) as a temporary fallback, and retail map9now exportstype=80 state_selector=1 chosen_frame=1instead of forcing frame0. - The renderer-side state parser is stronger too: the current
buildTypeStateFrameMapspath no longer treats eachDAT_800758ccselector as a flat first-word frame index. It now follows the currently verified sentinel control flow (0xfffe,0xfffd,0xfffc,0xfffb) when building selector-to-frame candidates from the exported state layer. - The top-level level-loader header is now tighter too.
wdl_resource_bundle_load_by_indexreads a0x38file header whose first nine dwords are section sizes, allocates a separate0x50runtime-header block atDAT_80067794, loads one extra non-contiguous blob atDAT_8006767c, and only then optionally inflates theDAT_8006b5d8source intoDAT_8006769c. FUN_80039dc4is now identified as the applier for thatDAT_80067794level runtime-header block, which means the map-viewer export still lacks one executable-backed level metadata lane even after the section-0 and per-type-bank work.- The current best read on that runtime-header lane is narrower now: because the downstream refresh helper is shared with input/menu paths too,
DAT_80067794looks more like per-level runtime mode/camera/presentation state than a hidden extra placement layer. - The state-script control flow is tighter too:
0xfffdis the direct in-family jump control,0xfffcswitches to a subsidiary script table entry immediately, and0xfffbis the scan-forward variant that consumes the next in-band0xfffdselector before switching. - The per-type bank provenance is tighter too.
psx_load_type_state_banksruns twice before the selectedLSET*.WDLopens and twice again after the level-local lanes are loaded, while the standalone late descriptor stream that fillsDAT_800758d8sits between the second pair. SoDAT_800758d8is definitely its own late bank, and the remainingDAT_800758d0/cc/d4lanes belong to the adjacent state-bank blobs rather than to the decompressed0x3e00map-state buffer. - The unresolved type path is more clearly dynamic than before:
psx_object_lookup_variant_entryis called from both constructors and frompsx_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
0x0042blocker too. The exportedstate_selectorlabel is confirmed to be raw wordu4, not a derived guess, whilelaneis raw wordu5. A full cache scan found3944type=0x0042placeholders across61maps and showed selectors0..4, with real3/4cases onmap-4,map-5,map-8,map-45,map-69, andmap-85. - That same cache scan shows lane and selector are not interchangeable.
0x0042still clusters mostly on lanes0x0020and0x0022, but there are alsolane=0x0030exports withstate_selector=0(for example onmap-108). So the next executable-backed pass should treatu4as the confirmed selector input andu5as a separate lane/class byte while tracing the post-advanceDAT_800758d4variant 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_scriptstores the authored selector inobj+0x9e, butpsx_object_lookup_variant_entryindexesDAT_800758d4withobj+0x94, the current script word. Two runtime paths (FUN_80028c94andFUN_8002906c) can reseat the active script fromFUN_8003bc1c(obj) >> 2 & 0xf, so at least some live0x0042selectors3and4come from heading-based runtime reselection rather than from the original record alone. - The newly traced consumer side narrows that further.
psx_object_advance_state_scriptsign-extends theDAT_800758d4lookup result intoobj+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 serializeDAT_800758d4as 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.
buildTypeCompanionExtentsMapsdecodes the exportedDAT_800758d4layer as au32 count + packed 3-byte signed extentstable, scenestateLayersnow preserve those decoded extents per type/state, and each exported PSX scene item andmapSourcerow now carries the resolvedcompanionExtentstuple 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+0x94interacts with the drawable resource atobj+0x10after post-spawn reselection. The next useful trace should therefore stay close to those family-specific presentation callers instead of spending more time on genericDAT_800758d4consumers. - The latest dispatch-table pass narrows that again for
0x0042and0x0049: they do not have unique per-type descriptor entries. Both sit inside the same generic descriptor cluster covering0x003e..0x0050, and their shared descriptor currently resolves topsx_spawn_compound_record_advance_state_once,psx_object_refresh_main_visible_and_cleanup, and the newly namedpsx_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_8002906cfirst 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 toFUN_80028c94or recomputes heading from motion state before callingpsx_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, andpsx_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 intoobj+0x60/+0x64/+0x68,psx_object_quantize_motion_heading16(0x8003bc1c) wraps the current object motion vector, andpsx_quantize_vector_heading16(0x8003b980) reduces that vector to one of 16 heading buckets before the caller maps it back to aDAT_800758ccscript selector. Combined with the already-verifiedpsx_object_advance_state_script -> psx_object_lookup_variant_entrypath, this means the art-facingDAT_800758d4lookup is driven by the current script word atobj+0x94, not directly by the original placement selector stored atobj+0x9e. - The local render-routing wrappers are named now too, which sharpens the practical exporter story.
psx_spawn_compound_record_advance_state_once(0x80013618) andpsx_spawn_simple_record_set_active_flag(0x8001372c) are constructor-side wrappers that immediately push new objects into specific live states, whilepsx_object_refresh_main_visible_and_cleanup(0x80013688) is a compact stage-1 projector/cleanup wrapper andpsx_object_advance_state_and_queue_special_visible(0x80013758) is a compact stage-2 wrapper that advances script state and immediately queues throughpsx_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+0x10before the object enters the live update loop; the current script word atobj+0x94is then passed straight throughpsx_resource_frame_origin_x/y,psx_resource_frame_width/height, and finally intopsx_sprite_resource_submit_frameorpsx_image_table_submit_frameduring 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_descriptorcreates the drawable resource from the per-type art descriptor, usingimage_resource_bind_vram_slotfor single-image type-4 resources andimage_bundle_load_to_vramfor 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, andpsx_main_visible_list_get_sorted_sliceare the concrete helpers behind the main visible-object pass thatpsx_draw_world_visible_passessubmits 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 -> framerule can be trusted, otherwise the viewer will keep drawing structurally correct but still unreadable placeholder-heavy scenes. JL-9already appears in recovered PSX weapon-name tables, but gameplay availability and sprite identity are not yet closed.
Success Criteria
Map-viewer success
- At least one PSX map loads in the existing viewer with stable world placement, defensible draw order, and recognizable room/layout structure.
- The PSX path reuses the existing viewer pipeline instead of creating a separate one-off viewer.
- Exported scene data preserves enough raw metadata to keep later decomp passes reversible.
Palette success
- Bundle export chooses the same palette family the runtime uses for that placement class.
- At least one tile-heavy scene and one object-heavy scene render with mostly correct colors without manual palette swapping.
- Palette selection logic is encoded in exporter metadata or viewer-side decode rules, not only in prose notes.
JL-9 success
JL-9is classified as one of: fully usable weapon, cut/incomplete leftover, menu-only string, or debug-only grant.- The unlock or acquisition path is identified from executable logic, data tables, or authored content.
- The weapon's sprite or best candidate art bundle is identified and documented.
Workstreams
1. Close the PSX map record format
Purpose: replace the invalid small top-level record stream == whole level assumption with a renderer-fed scene that includes the real bulk map substrate.
Tasks:
- Revisit the executable loader chain around the
LSET*.WDLstream consumer and name the section families loaded intoDAT_800678f4,DAT_80067720,DAT_800758cc/d0/d4/d8,DAT_800675f8, andDAT_8006769c. - Prove which loaded section is the small top-level object/dispatch list and which section holds the actual bulk map substrate.
- Recover the format and semantics of the compressed blob that
FUN_8003b00cinflates into the0x3e00level buffer. - Tie one concrete subordinate record family to the constructor inputs that feed object
+0x3c/+0x40/+0x44as16.16fixed-point coordinates. - Recover the bundle/frame binding rule for map placements well enough to stop relying on broad candidate pairing.
- Recover the draw-order or layer rule used when multiple map records overlap.
- Validate the corrected multi-section schema on at least
L0.WDLandL1.WDLso the decode is not overfit to one level.
Expected output:
- a stable PSX placement schema recorded in
docs/psx/ - one exporter that emits scene JSON in the same broad shape as the existing viewer pipeline
- one known-good reference map whose structure is visually recognizable
2. Close palette selection instead of guessing it
Purpose: make exported graphics match the runtime palette path automatically.
Tasks:
- Continue from the already identified texture draw helpers and the caller path that reads palette override metadata from the object field currently described as
+0xa0in the notes. - Determine whether the placement record itself, a second-stage runtime header, or a side table supplies the override palette index.
- Reconcile the live VRAM
row 0xF0 / x=0success case against the on-disk palette blob so the export path can reproduce the runtime source instead of only matching dumps. - Identify whether different bundle modes or resource classes use different CLUT selection rules.
- Add exporter-side palette metadata that preserves both bundle default palette and resolved placement palette.
- Validate against at least three anchor assets: one wall/floor-heavy tile set, one object sprite with obvious color identity, and one UI or portrait-like asset.
Expected output:
- a documented palette-selection rule in
docs/psx/ - exported PSX atlases or frame PNGs that no longer require manual palette picking for the common solved families
- a short unresolved list only for genuinely exceptional palette cases
3. Integrate the PSX decode into the existing map viewer
Purpose: stop treating PSX as a disconnected experiment and make it a first-class renderer source.
Tasks:
- Define one PSX scene format version that keeps raw decode fields visible while still fitting the current viewer's atlas-plus-scene model.
- Export one minimal but real PSX map scene from the solved map schema and load it through the existing viewer path.
- Compare the rendered result against in-game screenshots, captured VRAM/framebuffer evidence, or clearly identifiable room geometry.
- Tighten the exporter until one map reads coherently before trying to bulk-export the entire disc.
- Only after a coherent single-map success, generalize to more
LSETmaps and add any PSX-specific catalog or loader toggles the viewer needs.
Expected output:
- one coherent PSX map visible in the existing viewer
- one stable exporter path that can be iterated on without forking the viewer architecture
4. Investigate JL-9 as data, logic, and art
Purpose: close the question of whether JL-9 is real and what it corresponds to visually.
Tasks:
- Locate the PSX weapon-name table and the code/data structure that indexes into it.
- Identify the item or weapon definition row for
JL-9, including ammo type, flags, and any inventory/equipability markers. - Trace all code and data references to that row: mission rewards, cheats, debug grants, pickups, shop/loadout flow, or scripted usecode equivalents if present.
- Check whether
JL-9appears in the pre-alpha build under the same index and whether its surrounding data differs from retail. - Identify the sprite by following the weapon/item definition to the bundle/frame or icon resource it uses.
- Classify the result clearly: shipped and obtainable, shipped but gated/unused, or string/data leftover only.
Expected output:
- a short
docs/psx/note or section that states whetherJL-9is real - the acquisition or unlock path if one exists
- the best supported sprite or bundle match
Recommended Execution Order
- Finish map-record closure enough to bind placements to the right art.
- Replace the current
.cacheruntime-record probe premise with the corrected multi-section WDL model, then recover the runtime type/resource lookup that can replace the still-provisionalu0 -> bundle indexrule with real art binding. - Get one map loading coherently in the existing viewer.
- After the viewer path is grounded, use the now-stronger bundle identification flow to close
JL-9sprite identity and availability.
Immediate Next Batch
- In Ghidra, tighten the section-family naming around
DAT_800678f4,DAT_80067720, and the candidateDAT_8006b5d8source so the currentsection0_*labels can be promoted from exporter-safe names to exact loader names. - Record which helpers read
DAT_80067720, which apply the separateDAT_80067794runtime-header block, and which helpers read the decompressedDAT_8006769cbuffer now that the offline decode path is present in the cache. - Compare the rebuilt all-map exports against recognizable rooms and decide whether the remaining missing structure now lives mainly in the decoded
DAT_8006769cbuffer or in still-unrendered subordinate tables. - 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_800758d8comes from its own late descriptor stream, whileDAT_800758d0/cc/d4belong to the adjacentpsx_load_type_state_banksblobs. The remaining open question is the exact sub-split inside those state-bank blobs, not whether they come from the decompressed level-state lane. - 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/d4state 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 feedsFUN_80041144, because the executable now clearly maintains it separately from the main visible list. Current delta: the unresolved families are also clearly dynamic now becausepsx_object_lookup_variant_entryreruns afterpsx_object_advance_state_script; the next verified art-binding pass should therefore sample post-jump state, not only constructor-time selectors. - 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.
- Decode the
psx_object_advance_state_scriptsentinel 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:fffeis closed as an audio/effect dispatch throughFUN_8004061c,fffdis the direct indexed jump, andfffc/fffbare now separated as the immediate subsidiary-switch versus scan-forward subsidiary-switch pair. - 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.
- Locate the
JL-9weapon entry in the PSX executable tables and log its table index, surrounding weapon names, and all code/data xrefs. - Create a short follow-up note in
docs/psx/after the batch rather than burying the result only in Ghidra comments.
Documentation Rule For This Track
- Keep long-form findings in
docs/psx/psx.mdor another dedicated file underdocs/psx/. - Keep this file as the active plan and update it when a major blocker closes or the execution order changes.
- When
JL-9closes cleanly, give it its own short note underdocs/psx/instead of leaving it as one bullet in a larger map note.