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

84 lines
No EOL
5.8 KiB
Markdown

# 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.