diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 6577750..98e4425 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -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 diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 00ff3ce..726f829 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -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}");