docs(issues): #37 humanoid coat doesn't extend up to neck (env-var diagnostics committed)
Filed as #37 after a ~3 hr investigation that ruled out animation source, backface culling/winding, palette overlay, and head-GfxObj polygons. Confirmed: - Stub is from part 9 (upper torso/coat) post-AnimPartChange (gfx 0x0100120D) - Part 9's both surfaces ARE matched by our 2 TextureChanges - Server data complete; composition formula matches ACME + retail decomp Untested hypothesis space (next session): - Texture decode chain (compare our SurfaceDecoder vs ACME TextureHelpers) - Polygon-to-surface index off-by-one on part 9 - Multi-layer texture composition AC may do - UV mapping bug Diagnostic env vars committed to source for next-session reuse: - ACDREAM_HIDE_PART=N — hide specific humanoid part to localize bugs - ACDREAM_NO_CULL=1 — disable backface culling - ACDREAM_DUMP_CLOTHING=1 — dump APC + TC + per-part Surface chain coverage Bug was originally reported as "head/neck protrudes forward"; the apparent forward shift turned out to be an optical illusion from the missing coat collar. Math + cdb-ground-truth + ACME comparison confirmed the head placement is correct retail-faithful — see #37 for the long write-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3361641655
commit
09e013b7bd
3 changed files with 166 additions and 3 deletions
|
|
@ -168,6 +168,10 @@ public sealed class GameWindow : IDisposable
|
|||
// Keep the experimental path available for DAT archaeology only.
|
||||
private readonly bool _enableSkyPesDebug =
|
||||
string.Equals(Environment.GetEnvironmentVariable("ACDREAM_ENABLE_SKY_PES"), "1", StringComparison.Ordinal);
|
||||
|
||||
// Diagnostic: hide a specific humanoid part (>=10 parts) at render.
|
||||
private static readonly int s_hidePartIndex =
|
||||
int.TryParse(Environment.GetEnvironmentVariable("ACDREAM_HIDE_PART"), out var hp) ? hp : -1;
|
||||
private readonly HashSet<SkyPesKey> _activeSkyPes = new();
|
||||
private readonly HashSet<SkyPesKey> _missingSkyPes = new();
|
||||
|
||||
|
|
@ -1912,6 +1916,17 @@ public sealed class GameWindow : IDisposable
|
|||
// then proceed with the normal upload loop.
|
||||
var parts = new List<AcDream.Core.World.MeshRef>(flat);
|
||||
var animPartChanges = spawn.AnimPartChanges ?? Array.Empty<AcDream.Core.Net.Messages.CreateObject.AnimPartChange>();
|
||||
// Diagnostic: dump AnimPartChanges + TextureChanges for humanoid setups
|
||||
// gated on ACDREAM_DUMP_CLOTHING=1. Used to verify whether the server is
|
||||
// sending coverage for the neck (part 9 for Aluvian Male) etc.
|
||||
bool dumpClothing = string.Equals(Environment.GetEnvironmentVariable("ACDREAM_DUMP_CLOTHING"), "1", StringComparison.Ordinal)
|
||||
&& setup.Parts.Count >= 10;
|
||||
if (dumpClothing)
|
||||
{
|
||||
Console.WriteLine($"\n=== DUMP_CLOTHING: guid=0x{spawn.Guid:X8} name='{spawn.Name}' setup=0x{setup.Id:X8} APC={animPartChanges.Count} ===");
|
||||
foreach (var c in animPartChanges)
|
||||
Console.WriteLine($" APC part={c.PartIndex:D2} -> gfx=0x{c.NewModelId:X8}");
|
||||
}
|
||||
foreach (var change in animPartChanges)
|
||||
{
|
||||
if (change.PartIndex < parts.Count)
|
||||
|
|
@ -1932,6 +1947,45 @@ public sealed class GameWindow : IDisposable
|
|||
// to get a texture decoded with the replacement SurfaceTexture
|
||||
// substituted inside the Surface's decode chain.
|
||||
var textureChanges = spawn.TextureChanges ?? Array.Empty<AcDream.Core.Net.Messages.CreateObject.TextureChange>();
|
||||
if (dumpClothing)
|
||||
{
|
||||
Console.WriteLine($" TextureChanges count={textureChanges.Count}");
|
||||
foreach (var tc in textureChanges)
|
||||
Console.WriteLine($" TC part={tc.PartIndex:D2} oldTex=0x{tc.OldTexture:X8} -> newTex=0x{tc.NewTexture:X8}");
|
||||
|
||||
// For each part (post-AnimPartChange), dump its Surface chain so we
|
||||
// can see which OrigTextureIds the part references and check which
|
||||
// are covered by our TextureChanges.
|
||||
var tcByPart = new Dictionary<int, HashSet<uint>>();
|
||||
foreach (var tc in textureChanges)
|
||||
{
|
||||
if (!tcByPart.TryGetValue(tc.PartIndex, out var set)) { set = new HashSet<uint>(); tcByPart[tc.PartIndex] = set; }
|
||||
set.Add(tc.OldTexture);
|
||||
}
|
||||
for (int pi = 0; pi < parts.Count; pi++)
|
||||
{
|
||||
var pgfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(parts[pi].GfxObjId);
|
||||
if (pgfx is null) continue;
|
||||
if (pgfx.Surfaces.Count == 0) continue;
|
||||
tcByPart.TryGetValue(pi, out var coveredOldTex);
|
||||
int matched = 0;
|
||||
int unmatched = 0;
|
||||
var unmatchedList = new List<string>();
|
||||
foreach (var surfQid in pgfx.Surfaces)
|
||||
{
|
||||
uint surfId = (uint)surfQid;
|
||||
var surf = _dats.Get<DatReaderWriter.DBObjs.Surface>(surfId);
|
||||
if (surf is null) continue;
|
||||
uint origTex = (uint)surf.OrigTextureId;
|
||||
if (coveredOldTex is not null && coveredOldTex.Contains(origTex)) matched++;
|
||||
else { unmatched++; unmatchedList.Add($"surf=0x{surfId:X8} origTex=0x{origTex:X8}"); }
|
||||
}
|
||||
if (pgfx.Surfaces.Count > 0)
|
||||
Console.WriteLine($" part[{pi:D2}] gfx=0x{parts[pi].GfxObjId:X8} surfaces={pgfx.Surfaces.Count} matched={matched} unmatched={unmatched}");
|
||||
foreach (var s in unmatchedList)
|
||||
Console.WriteLine($" UNMATCHED {s}");
|
||||
}
|
||||
}
|
||||
Dictionary<int, Dictionary<uint, uint>>? resolvedOverridesByPart = null;
|
||||
if (textureChanges.Count > 0)
|
||||
{
|
||||
|
|
@ -6036,6 +6090,10 @@ public sealed class GameWindow : IDisposable
|
|||
partTransform = partTransform * scaleMat;
|
||||
|
||||
var template = ae.PartTemplate[i];
|
||||
if (s_hidePartIndex >= 0 && i == s_hidePartIndex && partCount >= 10)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
newMeshRefs.Add(new AcDream.Core.World.MeshRef(template.GfxObjId, partTransform)
|
||||
{
|
||||
SurfaceOverrides = template.SurfaceOverrides,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue