tools(probe): add RainMeshProbe — dumps rain mesh surface + polygons + build counts
Sibling of StarsProbe/WeatherEnumerator. Targets GfxObjs 0x01004C42 and
0x01004C44 (the two rain cylinders). For each: dumps the Surface raw
record (Type bits, Translucency, Luminosity, Diffuse, ColorValue,
OrigTextureId), every polygon's SidesType + Stippling + hasPos/hasNeg
emission flags (mirroring GfxObjMesh.Build's neg-side rule), and the
final GfxObjMesh.Build() submesh+index counts.
Built per independent code-review §5: "Run one targeted probe... if one
cylinder has more than 48 indices per side-equivalent, fix the
duplicate-side/cull behavior together with the surface-opacity uniform."
Probe results (rain_mesh_probe.log, not committed):
Surface 0x080000C5: Type=0x10112 (Base1Image|Translucent|Alpha|Additive),
Translucency=0.5000, Luminosity=0.1484, OrigTextureId=0x050016A6.
Polygons: all 8 are Stippling=Positive, SidesType=None, hasNeg=False.
Build output: 1 submesh, 24 verts, 48 indices = 8 walls × 2 tris × 3.
→ SINGLE-SIDED (the duplicate-side hypothesis is disconfirmed).
Confirmed: the rim brightness excess is purely from Translucency not
being plumbed (acdream draws rain at full alpha=1.0 instead of retail's
0.5). Bonus finding: surface.Luminosity=0.1484 is also ignored by the
renderer's `effEmissive = (luminosity > 0) ? luminosity : sub.SurfLuminosity`
fallback (the local `luminosity` defaults to 1.0 so the fallback never
fires) — but that's keyed on the LUMINOUS flag bit (0x40), which the rain
surface does NOT have. Filed as follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e0da496e0
commit
b8e0857b87
2 changed files with 172 additions and 0 deletions
157
tools/RainMeshProbe/Program.cs
Normal file
157
tools/RainMeshProbe/Program.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// 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);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
static void ProbeRain(DatCollection dats, 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, 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})");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tools/RainMeshProbe/RainMeshProbe.csproj
Normal file
15
tools/RainMeshProbe/RainMeshProbe.csproj
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>RainMeshProbe</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\AcDream.Core\AcDream.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue