# PSX Art-Binding Recovery ## Scope - Active target: retail PlayStation `SLUS_002.68` feeding the renderer-local cache pipeline in `Crusader-Map-Viewer/map_renderer`. - Goal of this pass: stop treating the current unreadable PSX output as a renderer-only problem and measure whether placeholders are mainly caused by extraction-time art binding failures. - This note records the first pragmatic recovery pass that replaces large placeholder bands with real bundle-backed art while keeping the result auditable in exported scene metadata. ## Root Cause Summary - The current unreadable output was primarily an extraction-side problem, not a front-end draw bug. - Before this pass, the built `.cache/scene-cache/psx-remorse` set carried `58,262` fallback placeholders versus only `1,714` bundle-mapped items. - The fallback concentration was overwhelmingly in `section0_constructor_placements` (`94.5%` of fallback items), not in the smaller `section0_dispatch_roots` family. - The hottest unresolved types were `0x0042`, `0x0041`, `0x0047`, `0x0045`, `0x003f`, `0x004b`, `0x0046`, `0x0044`, `0x0048`, and `0x004a`. - Those types already appeared in the exported `DAT_800758d8` art-template layer in many maps, but almost all of their rows were `blockSize = 0` and therefore had no direct payload dwords to match against bundle offsets. - The neighboring state banks were still populated: `DAT_800758cc` carried valid script tables for the same types, while `DAT_800758d0` stayed empty for this family and `DAT_800758d4` remained on the runtime-bounds side. - That combination strongly suggests inherited or aliased presentation for these constructor-placement families rather than a literal absence of art. ## Implemented Recovery Rule The cache builder now resolves PSX art in three stages instead of dropping directly from "no direct `DAT_800758d8` payload" to a placeholder: 1. Use the direct non-zero `DAT_800758d8` row when a type has a real local template payload and bundle match. 2. If the type lives in the unresolved zero-block constructor-placement family, look for a same-map donor type whose `DAT_800758cc` script blob is identical and whose `DAT_800758d8` row resolves to a real bundle. 3. If no exact script-signature donor exists, fall back to the nearest resolved same-map donor inside the current constructor-placement family band (`0x003e..0x0064`). The exporter keeps this explicit instead of pretending the rule is fully solved: - `mappingSource` now records whether the art came from a direct template, a `cc-signature-donor:xxxx`, or a `generic-family-donor:xxxx` path. - `templateTypeId` and `donorTypeId` are preserved in exported `mapSource` rows. - The unresolved type still uses its own `DAT_800758cc` selector-to-frame map; only the bundle source is borrowed. ## Measured Effect Two measured rebuilds mattered in this pass. ### First donor pass - Added donor reuse for the original generic family band. - Totals moved from `58,262` fallback / `1,714` bundle-mapped to `37,054` fallback / `22,922` bundle-mapped. Representative maps: - `map 0`: `1078 / 111` -> `340 / 849` - `map 9`: `664 / 111` -> `197 / 578` - `map 43`: `707 / 97` -> `196 / 608` - `map 64`: `1194 / 161` -> `736 / 619` - `map 104`: `909 / 93` -> `894 / 108` ### Extended donor band - Extended the same heuristic through the next zero-block constructor-placement band up to `0x0064`. - Final measured totals after rebuild: `25,038` fallback / `34,938` bundle-mapped. Representative maps after the extended pass: - `map 0`: `66` fallback / `1123` bundle-mapped - `map 9`: `42` fallback / `733` bundle-mapped - `map 43`: `9` fallback / `795` bundle-mapped - `map 64`: `160` fallback / `1195` bundle-mapped - `map 104`: `866` fallback / `136` bundle-mapped So the first practical conclusion is straightforward: the exporter was the main blocker for most maps, and a constrained donor heuristic can replace a large fraction of placeholders with real graphics without touching the viewer runtime. ## Remaining Unresolved Mass - The cache is still not fully closed. `25,038` fallback items remain across `62` maps. - The remaining fallback distribution is still concentrated in `section0_constructor_placements`. - Current top fallback-heavy types after the donor pass are `0x0042`, `0x005a`, `0x005b`, `0x005c`, `0x0047`, `0x0059`, `0x005d`, `0x005e`, `0x0062`, and `0x0063`. - `0x0042` remains the single largest unresolved type even after the heuristic pass. - `map 104` is the most obvious current outlier: it still sits at `866` fallback items versus only `136` bundle-mapped items, so the next recovery pass should treat it as the best stress case rather than relying only on the now-mostly-readable early maps. ## Practical Interpretation - The donor heuristic is good enough to prove that many placeholders were caused by missing extraction-time inheritance logic. - It is not strong enough to count as the final executable-backed rule for the unresolved families. - The remaining gap still sits where the earlier Ghidra work already pointed: somewhere between the constructor-side art/resource creation lane and the live post-spawn state/resource/frame reselection path. ## Next Steps 1. Trace the remaining high-volume band `0x0055..0x0063` in Ghidra with the same question used for `0x0042`: why does `DAT_800758d8` stay zero-sized while visible art still exists at runtime? 2. Use `map 104` as the primary regression target and dump its remaining fallback type/state/lane distribution before doing any broader heuristic expansion. 3. Compare unresolved zero-block types against nearby resolved donor types at the constructor/resource level, not only at the script-signature level, so borrowed bundles can be replaced with an executable-backed alias rule. 4. Keep the `DAT_800758d4` work on the bounds side unless a family-specific caller proves otherwise; this pass did not reopen that conclusion.