D120: emit the opcode-0x18 PointUsage[] s16 chain (was zero-filled)

d43_emit.py reserved the PointUsage region (add_region) during layout but
the emit pass never wrote it -> all zeros -> chr.c's blood-decal walk
'index = PointUsage[index]' cycled 0->0 forever (capped by the #ifdef
PORT guard at chr.c:3328, so decals were missing/wrong, not a hang).
Now byteswaps each s16 index (numVertices entries, negative-terminated,
no remap). Full regen chain re-run, round-trip validation passes. The
chr.c guard stays as a safety net (SMALL-FIXES B3). Also: retract RC4
(palette off-by-one) in TEXTURE-GLITCH-ANALYSIS -- the current RGBA5551
decode is correct; the doc's 'spec' was ARGB1555.
This commit is contained in:
James Dansereau committed 2026-08-31 10:59:26 -04:00
1 parent d0b17f1788
commit 742f2f820e
2 files changed
+30 -8

No files matched your search

+13 -8
View File
@@ -63,19 +63,24 @@
`G_TX_WRAP == 0` (`include/PR/gbi.h:391`) — dead code (already parked in
`docs/GRAPHICS-BACKLOG.md`).
### RC4 — Palette off-by-one (subtle global hue shift)
### RC4 — Palette off-by-one — **RETRACTED (M-30): the current code is correct.**
`port/fast3d/gfx_pc.cpp:835`:
The "N64 RGBA16 spec" line below is **ARGB1555**, which is *not* what the N64 uses.
N64 `G_IM_FMT_RGBA` / `G_IM_SIZ_16b` is **RGBA5551**: `RRRRR GGGGG BBBBB A`
(R = bits 15-11, G = 10-6, B = 5-1, A = bit 0).
```c
// fast3d (current): r = v>>11; g = (v>>6)&0x1f; b = (v>>1)&0x1f; a = v&1
// N64 RGBA16 spec: r = (v>>10)&0x1f; g = (v>>5)&0x1f; b = v&0x1f; a = v>>15
// fast3d (current, CORRECT): r = v>>11; g = (v>>6)&0x1f; b = (v>>1)&0x1f; a = v&1
// the "spec" line here was wrong: r = (v>>10)&0x1f; g = (v>>5)&0x1f; b = v&0x1f; a = v>>15
```
Channels are read one bit off (alpha treated as LSB). Grays (r≈g≈b) survive nearly intact —
which is why BUNKER1 looks "mostly right" (it's gray/brown) — but saturated hues shift.
Format mapping itself was verified correct: `TEXFORMAT_RGBA16_CI8/CI4` → `G_TT_RGBA16`,
`IA16_CI8/CI4` → `G_TT_IA16` (`src/game/image.c:71–104`).
Both `palette_to_rgba32` (`gfx_pc.cpp:~835`) and `import_texture_rgba16`
(`~651`) use the same RGBA5551 decode and agree with each other. The palette is
`PD_BE16`-swapped at load (`gfx_dp_load_tlut`), so `palentry` is already
correctly ordered. **Applying the "fix" would rotate every channel and move
alpha to the wrong bit — do not do it.** If a hue problem is ever confirmed
in-game, it is upstream (TLUT byte order at the converter, or a wrong
`rdp.palette_fmt`), not this function.
## 3. Depot-specific analysis ("why blue ground, only there?")
+17
View File
@@ -341,6 +341,7 @@ def process(name):
vtx_regions.append((cvo, ncv)); dstpos = add_region(cvo, 16 * ncv, dstpos)
if nv and puo:
dstpos = add_region(puo, 2 * nv, dstpos)
op24_pointusage[puo] = nv
elif op == 22:
nv = struct.unpack_from(">i", src, data)[0]; vo = be32o(src, data + 4)
if nv and vo:
@@ -629,6 +630,16 @@ def process(name):
else:
emit_main_vtx(vo, nv, npos)
# D120: PointUsage[] -- an `s16` next-index chain (numVertices entries,
# negative-terminated). Plain byteswap, no remap.
for puo, punv in op24_pointusage.items():
pnpos = remap(puo)
if pnpos is None:
errors.append(f"{name}: PointUsage {puo:#x} not in region map")
continue
for k in range(punv):
put_s16(pnpos + 2 * k, be16(src, puo + 2 * k))
# GDLs (pack order: unvisited first, then visit order), 16B LE slots
for g in all_gdls:
end = gdl_end(src, D, g)
@@ -675,6 +686,11 @@ def process(name):
# collision-vertex set (filled during layout; emit pass needs it)
op24_is_collision = {}
# D120: opcode-0x18 PointUsage arrays -> {old_off: numVertices}. Reserved in
# layout (add_region) but the emit pass must byteswap the s16 index chain or the
# runtime blood-decal walk (chr.c PointUsage[index]) cycles (guarded, not fixed,
# until now).
op24_pointusage = {}
def validate(name, src, D, NS, NT, nodes, R0, placed, all_gdls, buf, D_PC,
regions, node_newoff, rec_newoff, gdl_newoff, remap, inv_remap):
@@ -764,6 +780,7 @@ stats["table_models"] = len(model_names)
for name in model_names:
# reset per-file collision set before layout (populated below)
op24_is_collision.clear()
op24_pointusage.clear()
process(name)
if errors: