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

@ -1258,6 +1258,104 @@ If hypothesis (a) is correct, this issue effectively rolls into **#28** — the
---
## #47 — Humanoid Setup 0x02000001 renders bulky / lacks shape detail vs retail
**Status:** OPEN
**Severity:** MEDIUM (cosmetic — characters readable but visibly different from retail)
**Filed:** 2026-05-06
**Component:** rendering / mesh / character animation
**Description:** Every humanoid character using Setup `0x02000001`
(Aluvian Male) renders in acdream with a "bulky, less-defined" silhouette
compared to retail's view of the same character. Specifically: shoulders
look smoother/rounder where retail has pointier shoulder pads; back has
less contour; arms appear puffier. The effect is identical for player
characters (`+Acdream`, `+Je`) and for humanoid NPCs using the same
setup (e.g. Woodsman, Sedor Wystan the Blacksmith, Thelnoth Cort).
Drudges and other monster setups (e.g. `0x020007DD`) render
identically to retail, so this is *not* a pipeline-wide bug.
The bug is independent of equipment — `+Je` stripped naked still
shows the same bulky silhouette.
**Investigation 2026-05-06 (~3 hr session, ruled out many hypotheses):**
What was ruled out:
- **0xF625 ObjDescEvent appearance updates being dropped.** Was a real
bug for skin/hair colors; fixed in commit e471527. Does not affect
the bulky-shape issue (which persists with the fix in place and
with no equipment).
- **Position-pop on equip toggle.** Caused by re-applying with cached
spawn's stale position; fixed in same commit. Doesn't affect shape.
- **Clothing/armor overlapping the base body** (HiddenParts hypothesis).
User stripped naked; bulky shape persists.
- **ParentIndex hierarchy not walked in `SetupMesh.Flatten`.** Setup
`0x02000001` has a real hierarchy (`-1, -1, 1, 2, 3, -1, 5, 6, 7, 0,
9, 10, 11, 12, 13, 14, 15, 0, ...`), but implementing parent-walk
produced **no visible change** — confirming AC's idle animation
frames are already in setup-root coordinates, not parent-local.
- **Equipment / wielded items.** No equipment on `+Je` and bug persists.
- **Player-specific data flow.** Humanoid NPCs using same setup
(Woodsman) show same bug.
What was confirmed (data captured via `ACDREAM_DUMP_CLOTHING=1`):
- Setup `0x02000001`: `setup.Parts.Count = 34`, `flatten.Count = 34`,
`APC = 34..38` depending on equipment.
- All 34 parts emit triangles successfully (no silent GfxObj load
failures). Total ~648-700 tris per character.
- Idle animation frames place parts at sensible humanoid Z-heights
(head Z=1.587, mid-body Z=0.5-1.0, ground Z=0.085).
- Per-part orientations are nearly all 180° around -Z (W≈0,
Z≈-1) — a setup-wide coordinate-flip convention. Drudges have
varied per-part orientations.
- `setup.DefaultScale.Count = 0` for both humans and drudges → all
parts use Vector3.One scale.
**Working hypotheses (next session):**
1. **Per-vertex normal style.** AC dat may store per-face normals
for human GfxObjs (one normal per polygon, copied to all 3
vertices) but smooth normals for monster GfxObjs. acdream uses
dat normals directly. Test by computing smooth normals from face
adjacency and comparing render. User said "not shaders" but the
screenshots clearly show smooth-vs-faceted lighting differences.
2. **Lighting setup.** Cell ambient may be too low, leaving back-
facing surfaces in flat shadow. Compare `uCellAmbient` value
against retail's behaviour at the same time-of-day.
3. **Anti-aliasing.** Retail may use MSAA; acdream window may not.
Polygon edges in acdream would be visibly stair-stepped, reading
as "more faceted" / blockier.
4. **Surface flags interpretation.** Specific Surface.Type bits for
character textures (skin, fabric) may need handling acdream
doesn't yet do (e.g. `SmoothShade` flag, or a mip bias).
**Diagnostic infrastructure landed this session** (env-var-gated, no
runtime cost when off):
- `ACDREAM_DUMP_CLOTHING=1` extended:
- `setup.Parts.Count`, `flatten.Count`, `APC` count on header line
- `ParentIndex[]` array dump
- `DefaultScale[]` array dump
- `IdleFrame.Frames[]` per-part Origin + Orientation (first 17 parts)
- `EMIT part=NN gfx=0xXX subMeshes=N tris=N` per part
- `TOTAL tris=N meshRefs=N` per entity
**Files (suspect surface area for next investigation):**
- `src/AcDream.Core/Meshing/SetupMesh.cs` — Flatten composition
- `src/AcDream.Core/Meshing/GfxObjMesh.cs` — polygon emission +
vertex normal handling (line 142)
- `src/AcDream.App/Rendering/Shaders/mesh.frag` — lighting eq
- `src/AcDream.App/Rendering/Shaders/mesh.vert` — normal transform
**Acceptance:** Side-by-side screenshots of `+Acdream` (or any humanoid
NPC using `0x02000001`) viewed from the same angle in acdream and
retail show matching silhouette and shape definition.
---
## #46 — Retail observer of acdream sees blippy / laggy movement
**Status:** OPEN

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}");