docs(issues): file #47 (humanoid bulky-shape bug); land DUMP_CLOTHING diagnostics

Filed #47 in docs/ISSUES.md — humanoid characters using Setup 0x02000001
(players + Woodsman + other Aluvian NPCs) render visibly bulkier and less
shape-defined than retail's view. Drudges and other monster setups render
identically. Independent of equipment (naked +Je still shows it).
Investigation this session ruled out 0xF625 ObjDescEvent drops (real bug,
fixed in e471527, but doesn't explain shape), HiddenParts overlap,
ParentIndex walking (animation frames are setup-root coords already),
and player-specific data flow (NPCs using same setup affected too).

Diagnostic infrastructure landed alongside the issue (env-var-gated, no
runtime cost when off):
- ACDREAM_DUMP_CLOTHING=1 now also prints:
  - setup.Parts.Count, flatten.Count, APC count on header
  - ParentIndex[] and DefaultScale[] arrays
  - IdleFrame per-part Origin + Orientation (first 17 parts)
  - per-part EMIT line: gfx, subMeshes count, triangle count
  - TOTAL triangle / meshRef counts per entity
This is what nailed down "all 34 parts emit" + "animation frames are
setup-root not parent-local" + "humans get setup-wide 180°-Z rotation
that drudges don't" — saved hours next session.

Open hypotheses for #47 next session: per-face vs smoothed vertex
normals (per-vertex normals from dat may be face-style for human
GfxObjs but smooth for monsters), low cell ambient leaving back faces
flat-shadowed, missing MSAA on the GL window.
This commit is contained in:
Erik 2026-05-06 11:30:41 +02:00
parent e471527924
commit e697a9ad1e
2 changed files with 125 additions and 0 deletions

View file

@ -1997,6 +1997,33 @@ public sealed class GameWindow : IDisposable
if (dumpClothing)
{
Console.WriteLine($"\n=== DUMP_CLOTHING: guid=0x{spawn.Guid:X8} name='{spawn.Name}' setup=0x{setup.Id:X8} setup.Parts.Count={setup.Parts.Count} flatten.Count={flat.Count} APC={animPartChanges.Count} ===");
// Dump the Setup's ParentIndex + DefaultScale arrays to verify hierarchy.
var parentStr = string.Join(",", setup.ParentIndex.Take(Math.Min(34, setup.ParentIndex.Count)).Select(p => p == 0xFFFFFFFFu ? "-1" : p.ToString()));
Console.WriteLine($" ParentIndex[{setup.ParentIndex.Count}]: {parentStr}");
var scaleStr = string.Join(",", setup.DefaultScale.Take(Math.Min(34, setup.DefaultScale.Count)).Select(s => $"({s.X:F2},{s.Y:F2},{s.Z:F2})"));
Console.WriteLine($" DefaultScale[{setup.DefaultScale.Count}]: {scaleStr}");
// Dump the resolved idle frame's per-part Origin + Orientation.
// If retail composes parent_world * animation_local but acdream
// treats animation_local as world-relative, we'd see specific
// patterns of non-zero per-part origins/rotations that should
// be parent-relative. For setups whose idle has all parts at
// (0,0,0)/identity, parent walking would be a no-op (which
// matches my earlier "no change" experiment if that was the
// human-idle case) — diagnostic confirms.
if (idleFrame is not null)
{
Console.WriteLine($" IdleFrame.Frames[{idleFrame.Frames.Count}]:");
int dumpCount = Math.Min(idleFrame.Frames.Count, 17); // first 17 (real body parts, not the 17-33 placeholders)
for (int fi = 0; fi < dumpCount; fi++)
{
var f = idleFrame.Frames[fi];
Console.WriteLine($" [{fi:D2}] Origin=({f.Origin.X:F3},{f.Origin.Y:F3},{f.Origin.Z:F3}) Orient=(W={f.Orientation.W:F3} X={f.Orientation.X:F3} Y={f.Orientation.Y:F3} Z={f.Orientation.Z:F3})");
}
}
else
{
Console.WriteLine($" IdleFrame: NULL");
}
foreach (var c in animPartChanges)
Console.WriteLine($" APC part={c.PartIndex:D2} -> gfx=0x{c.NewModelId:X8}");