// RainMeshProbe — independent code-review recommended probe (Bug A, post-#26). // // Per Report 1's §5: "Run one targeted probe for 0x01004C42/0x01004C44: print // surface raw type/translucency, each polygon's SidesType/Stippling, and // GfxObjMesh.Build() submesh/index counts. If one cylinder has more than 48 // indices per side-equivalent, fix the duplicate-side/cull behavior together // with the surface-opacity uniform." // // The cylinder has 8 wall quads. With fan-triangulation each quad → 2 tris → // 6 indices, total 48 indices per side. If pos-only emission: 48. If pos+neg: // 96. The threshold tells us whether double-sided drawing is happening. using System; using System.IO; using System.Linq; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Options; using DatReaderWriter.Types; using AcDream.Core.Meshing; using SysEnv = System.Environment; string datDir = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine(SysEnv.GetFolderPath(SysEnv.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); Console.WriteLine($"datDir = {datDir}"); using var dats = new DatCollection(datDir, DatAccessType.Read); uint[] gfxIds = { 0x01004C42u, 0x01004C44u }; foreach (uint gid in gfxIds) ProbeRain(dats, gid); // Phase 7c: also dump every sky surface we know to test the LUMINOUS flag. // Two existing code comments contradict each other about whether Dereth's // dome/sun/moon meshes carry the LUMINOUS bit. Resolve empirically. Console.WriteLine(); Console.WriteLine("================ Sky Surface LUMINOUS audit ================"); uint[] skySurfaceIds = { 0x08000048u, 0x08000049u, 0x0800004Au, 0x0800004Bu, // dome 0x010015EE 0x0800004Du, // star sheet 0x010015EF 0x0800004Eu, 0x0800004Fu, 0x08000050u, 0x08000051u, // dome 0x010015F0 0x08000053u, 0x08000054u, 0x08000055u, 0x08000056u, // dome 0x010015F1 0x08000057u, 0x08000058u, 0x08000059u, 0x0800005Au, // dome 0x010015F2 0x080000D1u, // celestial 0x01001348 0x080000D2u, // sun-like 0x01001F67 0x080000D6u, 0x080000D7u, // moon 0x01001F6A 0x080000D4u, // cloud 0x01004C36/37 0x08000023u, // cloud 0x01004C35 0x08000024u, 0x08000025u, // cloud 0x01004C39/3A 0x080000D5u, // dome variant 0x010015B6 0x080000C5u, // RAIN — control row, expected NO Luminous }; foreach (uint sid in skySurfaceIds) ProbeSkySurface(dats, sid); return 0; static void ProbeSkySurface(DatCollection dats, uint sid) { if (!dats.TryGet(sid, out var s) || s is null) { Console.WriteLine($" Surface 0x{sid:X8} (NOT FOUND)"); return; } uint t = (uint)s.Type; bool luminous = (t & 0x40u) != 0u; Console.Write($" Surface 0x{sid:X8} Type=0x{t:X8} Luminous={(luminous ? "YES" : "no ")} Lum={s.Luminosity:F4} Trans={s.Translucency:F4} Diff={s.Diffuse:F4} "); // Decode bits inline. var bits = new (uint mask, string n)[] { (0x01u,"B1Solid"),(0x02u,"B1Image"),(0x04u,"B1ClipMap"),(0x10u,"Translucent"), (0x20u,"Diffuse"),(0x40u,"Luminous"),(0x100u,"Alpha"),(0x200u,"InvAlpha"), (0x10000u,"Additive"),(0x20000u,"Detail"), }; Console.WriteLine(string.Join("|", bits.Where(b => (t & b.mask) != 0).Select(b => b.n))); } static void ProbeRain(DatCollection dats, uint gid) { Console.WriteLine(); Console.WriteLine($"================ GfxObj 0x{gid:X8} ================"); if (!dats.TryGet(gid, out var go) || go is null) { Console.WriteLine(" (NOT FOUND)"); return; } Console.WriteLine($" Flags={go.Flags}"); Console.WriteLine($" VertexArray.Vertices.Count={go.VertexArray?.Vertices.Count ?? 0}"); Console.WriteLine($" Polygons.Count={go.Polygons?.Count ?? 0}"); Console.WriteLine($" Surfaces.Count={go.Surfaces?.Count ?? 0}"); Console.WriteLine($" PhysicsPolygons.Count={go.PhysicsPolygons?.Count ?? 0}"); Console.WriteLine($" SortCenter=({go.SortCenter.X:F2},{go.SortCenter.Y:F2},{go.SortCenter.Z:F2})"); // ----- Per-Surface dump ----- Console.WriteLine(); Console.WriteLine(" --- Surfaces (raw dat record) ---"); if (go.Surfaces is { Count: > 0 }) { for (int i = 0; i < go.Surfaces.Count; i++) { uint sid = (uint)go.Surfaces[i]; Console.WriteLine($" Surface[{i}] = 0x{sid:X8}"); if (!dats.TryGet(sid, out var surf) || surf is null) { Console.WriteLine(" (Surface NOT FOUND)"); continue; } uint typeRaw = (uint)surf.Type; Console.WriteLine($" Type=0x{typeRaw:X8} ({surf.Type})"); Console.WriteLine($" decoded bits:"); DumpFlagBits(typeRaw); Console.WriteLine($" Translucency={surf.Translucency:F4} (1.0 - x = opacity = {1f - surf.Translucency:F4})"); Console.WriteLine($" Luminosity={surf.Luminosity:F4}"); Console.WriteLine($" Diffuse={surf.Diffuse:F4}"); Console.WriteLine($" ColorValue=" + (surf.ColorValue is null ? "null" : $"A:{surf.ColorValue.Alpha} R:{surf.ColorValue.Red} G:{surf.ColorValue.Green} B:{surf.ColorValue.Blue}")); Console.WriteLine($" OrigTextureId=0x{(uint)surf.OrigTextureId:X8}"); Console.WriteLine($" OrigPaletteId=0x{(uint)surf.OrigPaletteId:X8}"); } } // ----- Per-Polygon dump ----- Console.WriteLine(); Console.WriteLine(" --- Polygons (sides + stippling — checks Report 1 hypothesis) ---"); if (go.Polygons is { Count: > 0 }) { int posCount = 0, negCount = 0; foreach (var kv in go.Polygons) { var p = kv.Value; // Mirror the GfxObjMesh.Build() emission rule (lines 71-91): bool hasPos = !p.Stippling.HasFlag(StipplingType.NoPos); bool hasNeg = p.Stippling.HasFlag(StipplingType.Negative) || p.Stippling.HasFlag(StipplingType.Both) || (!p.Stippling.HasFlag(StipplingType.NoNeg) && p.SidesType == CullMode.Clockwise); if (hasPos) posCount++; if (hasNeg) negCount++; Console.WriteLine( $" Poly[{kv.Key,3}] VertexIds={p.VertexIds.Count} " + $"PosSurface={p.PosSurface} NegSurface={p.NegSurface} " + $"Stippling={p.Stippling} SidesType={p.SidesType} " + $"hasPos={hasPos} hasNeg={hasNeg} " + $"PosUVIdx={p.PosUVIndices.Count} NegUVIdx={p.NegUVIndices.Count}"); } Console.WriteLine($" Build emission summary: pos-side polys={posCount} neg-side polys={negCount}"); } // ----- GfxObjMesh.Build() output ----- Console.WriteLine(); Console.WriteLine(" --- GfxObjMesh.Build() output ---"); var subs = GfxObjMesh.Build(go, dats); Console.WriteLine($" Submesh count: {subs.Count}"); int totalVerts = 0, totalIndices = 0; for (int i = 0; i < subs.Count; i++) { var s = subs[i]; totalVerts += s.Vertices.Length; totalIndices += s.Indices.Length; Console.WriteLine( $" Submesh[{i}] SurfaceId=0x{s.SurfaceId:X8} " + $"Vertices={s.Vertices.Length} Indices={s.Indices.Length} " + $"Translucency={s.Translucency} Luminosity={s.Luminosity:F2} " + $"NeedsUvRepeat={s.NeedsUvRepeat}"); } Console.WriteLine($" TOTAL: verts={totalVerts} indices={totalIndices}"); Console.WriteLine(); Console.WriteLine($" Report 1 threshold check: with 8 wall quads × 2 tris × 3 indices = 48 indices per side."); Console.WriteLine($" pos-only emission expects ~48 indices total."); Console.WriteLine($" pos+neg emission expects ~96 indices total."); Console.WriteLine($" OBSERVED: {totalIndices} indices → " + (totalIndices > 60 ? "*** DOUBLE-SIDED — duplicate-side rendering active ***" : "single-sided")); } static void DumpFlagBits(uint type) { // From docs/research/named-retail/acclient.h:5820-5836. // Print every named SurfaceType bit that's set. var bits = new (uint mask, string name)[] { (0x00000001u, "Base1Solid"), (0x00000002u, "Base1Image"), (0x00000004u, "Base1ClipMap"), (0x00000010u, "Translucent"), (0x00000020u, "Diffuse"), (0x00000040u, "Luminous"), (0x00000100u, "Alpha"), (0x00000200u, "InvAlpha"), (0x00010000u, "Additive"), (0x00020000u, "Detail"), (0x10000000u, "Gouraud"), (0x40000000u, "Stippled"), (0x80000000u, "Perspective"), }; foreach (var (mask, name) in bits) { if ((type & mask) != 0) Console.WriteLine($" {name} (0x{mask:X8})"); } }