Crusader_Decomp/docs/psx/art-binding-recovery.md
2026-04-07 17:16:44 +02:00

5.8 KiB

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.