docs(issues): #37 — Investigation 2 narrows bug to SubPalette coverage gaps

Five parallel agents + dat probes ruled out:
- byte-level decode primitive (matches ACViewer)
- polygon emission (no ST_DOUBLE / Surface.Type & 6 issues)
- per-PART texture-override scoping (correctly per-MeshRef'd)
- SubPalette indexing convention (full-size 2048 palettes, *8 wire un-pack
  is single-applied)

Smoking gun: for +Acdream the server sends 10 SubPaletteSwap ranges that
overlay palette indices [0..320), [576..1024), [1392..1488), [1728..1920).
The complement — [320..576), [1024..1392), [1488..1728), [1920..2048) —
is NOT overlaid. Base palette 0x0400007E at those indices has
red/skin tones. Coat texture UVs sampling those non-overlaid indices
render as visible "skin stub at top of coat".

Either ACE sends incomplete SubPaletteSwap data, or retail does extra
client-side ClothingTable computation we (and ACE) don't.

Diagnostic harness now lives at tools/InspectCoatTex/Program.cs;
GameWindow's DUMP_CLOTHING also probes runtime SubPalette dat sizes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-05 14:45:50 +02:00
parent a3f53c2644
commit 5937ebe1c5
3 changed files with 368 additions and 16 deletions

View file

@ -2008,7 +2008,33 @@ public sealed class GameWindow : IDisposable
if (spawn.SubPalettes is { } subPaletteList)
{
foreach (var subPal in subPaletteList)
Console.WriteLine($" SP id=0x{subPal.SubPaletteId:X8} offset={subPal.Offset} length={subPal.Length}");
{
int rawOffset = subPal.Offset * 8;
int rawLen = subPal.Length == 0 ? 2048 : subPal.Length * 8;
var pal = _dats.Get<DatReaderWriter.DBObjs.Palette>(subPal.SubPaletteId);
string palInfo = pal is null ? "Palette dat NOT FOUND (might be PaletteSet 0x0F?)" : $"Colors.Count={pal.Colors.Count}";
Console.WriteLine($" SP id=0x{subPal.SubPaletteId:X8} wireOffset={subPal.Offset} wireLength={subPal.Length} -> rawIdx[{rawOffset}..{rawOffset + rawLen}) {palInfo}");
// If pal is non-null and small, show first 4 colors
if (pal is not null && pal.Colors.Count > 0)
{
int sample = Math.Min(4, pal.Colors.Count);
for (int s = 0; s < sample; s++)
{
var c = pal.Colors[s];
Console.WriteLine($" pal[{s:D3}] R={c.Red:X2} G={c.Green:X2} B={c.Blue:X2}");
}
// Also probe at the rawOffset (if in range) — that's where overlay copies FROM in our code
if (rawOffset < pal.Colors.Count)
{
var c = pal.Colors[rawOffset];
Console.WriteLine($" pal[{rawOffset:D4}] R={c.Red:X2} G={c.Green:X2} B={c.Blue:X2} <-- our code reads here");
}
else
{
Console.WriteLine($" pal[{rawOffset:D4}] OUT OF RANGE (Colors.Count={pal.Colors.Count}) -- our code's read SKIPS the overlay !!");
}
}
}
}
}
foreach (var change in animPartChanges)