Fixed palettes

This commit is contained in:
MaddoScientisto 2026-04-18 14:38:40 +02:00
commit 93bc6e7a07
6 changed files with 328 additions and 100 deletions

View file

@ -201,27 +201,81 @@ For each record:
- `screenX - originX`
- `screenY - originY`
Current draw order is conservative:
### Projection sign convention
- main-visible before special-visible
- then ascending `screenY`
- then ascending `screenX`
`psx_project_object_main_visible @ 0x80040d44` writes `proj_x = Y - X` and
`proj_y = 2*Z - (X + Y) / 2` to `obj+0x78/0x7c`. The engine's draw step at
`0x80040e3c/0x80040e5c` then computes `screen = (cam - proj) - origin`, which
flips the sign of the projection relative to canvas Y-down space. For a
camera-less full-map export the exporter bakes that flip into
`projectCtorPlacement`, so higher world-Z and smaller (X+Y) sit visibly higher
on the output PNG. The same projection is applied to both constructor
placements and dispatch-root records; dispatch-root X/Y fields are in world
coordinates, not pre-projected screen coordinates, despite the runtime
`camera +/- 0x140` cull comparing against them directly.
This is a probe approximation. The later graph-based stage-1 ordering still belongs to a future pass.
### Authored layer semantics
The rendered PNG uses a neutral opaque background by default so probe silhouettes are legible without relying on transparency.
The two authored lanes carry different responsibilities:
- `constructors`: static level geometry — walls, floors, architecture placed
by `psx_dispatch_section0_constructor_placements`.
- `roots`: interactive / dynamic objects — crates, terminals, doors, pickups
placed by `psx_dispatch_section0_dispatch_roots`.
Despite the dispatcher name, the `roots` lane is not the map background; it is
the live-object seed list. For the exporter, "constructors" is the geometry
layer and "roots" is the object layer.
### Painter's order
The exporter sorts items before blitting using the following keys, in order:
1. `stage` ascending. `stage = 1` when `typeWord === 4` or `laneWord & 0x0400`
is set; those overlays draw last.
2. Authored-layer priority: `constructors` (0) before `roots` (1). Static
geometry draws first so interactive props in the same room do not get
hidden behind the floor or wall pieces that occupy the same cell.
3. Isometric depth ascending: back-to-front by world `X + Y` (isometric
ground-depth axis). Falls back to projected `screenY` when world
coordinates are unavailable.
4. World `Z` ascending within the same ground cell so lower elevations draw
before taller objects sharing the same footprint.
5. `screenX` ascending as a stable tie-breaker.
This is still an approximation of the engine's stage-1 graph order but is
closer to what an isometric painter's algorithm would produce than the earlier
screenY-only sort.
The rendered PNG uses a neutral opaque background by default so probe
silhouettes are legible without relying on transparency.
## Color Rule
`v0` emits grayscale art from raw pixel indices.
The exporter resolves palettes entirely from the WDL contents. It does not
require any RAM or VRAM dump; those paths are now optional research overrides.
Reason:
The map-local palette blob lives at `headerWords[2] .. headerWords[2] + 0x1000`
(4096 bytes = 2048 colors = 128 × 16-entry CLUTs). The blob is what the engine
uploads to VRAM rows `0xf0 .. 0xf7` on map load; each VRAM row is 16 CLUTs
wide so the 128 CLUTs tile exactly 8 rows of 16 CLUTs.
- bundle frame decode is already well constrained
- full CLUT parity is not
- grayscale preserves shape/variant evidence without pretending the palette problem is solved
Resolution rules by bundle mode:
Transparent index `0` stays transparent.
- **Mode 2 (4bpp)**: the bundle header's `paletteIndex` at `+0x14` is the
16-entry CLUT index into the WDL `palettes16` bank. When that index points
at a sparse/empty CLUT the exporter falls back to a per-bundle palette sweep
that picks a CLUT covering the pixel-index set used by the bundle frames.
- **Mode 1 (8bpp)**: the 256-color CLUT is the concatenation of 16 consecutive
16-entry CLUTs from the WDL bank. The bundle's `paletteIndex` is treated as
the starting CLUT index. For the current L0 dataset every mode-1 bundle
stores `paletteIndex = 0`, which is the top-left 256-color bank. Mode-1
color fidelity is therefore approximate until the level-specific 256-CLUT
source (suspected to live in the `stateBank` block) is decoded — tracked as
a follow-up.
Transparent pixel index `0` stays transparent during blit regardless of the
color value stored at CLUT index 0.
## CLI
@ -254,8 +308,13 @@ Supported options:
## Planned Follow-Ups
- extend `sceneInterpretation` so it reflects the landed loader-faithful binding instead of the older repeated-wrong-art warning
- identify and parse the separate static-world or subordinate level substrate that complements the constructor-fed live-object lane, instead of treating section-0 constructor placements as the whole map
- add palette/CLUT reconstruction
- add stage-1 graph ordering recovery
- compare the probe scene against fixed live samples such as `map 104` without reintroducing viewer-side donor assumptions
- decode the `stateBank` and `stateBank2` blocks to recover the level-specific
256-color CLUT used by mode-1 sprites. Current mode-1 palettes default to
CLUT-bank start 0, which produces plausible colors for some sprites but
renders many indoor floor tiles as solid green plates.
- extend `sceneInterpretation` so it reflects the landed loader-faithful
binding instead of the older repeated-wrong-art warning.
- recover the engine's stage-1 graph ordering instead of approximating with
isometric `(X + Y, Z, screenX)` sort keys.
- compare the probe scene against fixed live samples such as `map 104` without
reintroducing viewer-side donor assumptions.