acdream/tools/RainMeshProbe/Program.cs

199 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.Content;
using AcDream.Core.Content;
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);
using var datSource = new DatCollectionAdapter(dats);
uint[] gfxIds = { 0x01004C42u, 0x01004C44u };
foreach (uint gid in gfxIds) ProbeRain(dats, datSource, 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<Surface>(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, IDatObjectSource datSource, uint gid)
{
Console.WriteLine();
Console.WriteLine($"================ GfxObj 0x{gid:X8} ================");
if (!dats.TryGet<GfxObj>(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<Surface>(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, datSource);
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})");
}
}