fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s

This commit is contained in:
Erik 2026-08-28 20:01:39 +02:00
parent d3df4cb20a
commit f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions

View file

@ -3,6 +3,7 @@ using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
using DatReaderWriter.Options;
using DatEnvironment = DatReaderWriter.DBObjs.Environment;
using SysEnv = System.Environment;
string datDir = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR")
@ -16,7 +17,11 @@ if (!Directory.Exists(datDir))
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
if (args.Length > 0 && string.Equals(args[0], "buildings", StringComparison.OrdinalIgnoreCase))
if (args.Length > 0 && string.Equals(args[0], "cell-winding-catalog", StringComparison.OrdinalIgnoreCase))
{
return AuditCellWindingCatalog(dats);
}
else if (args.Length > 0 && string.Equals(args[0], "buildings", StringComparison.OrdinalIgnoreCase))
{
uint landblockId = args.Length > 1 ? ParseHex(args[1]) : 0xA9B40000u;
int radius = args.Length > 2 ? int.Parse(args[2], System.Globalization.CultureInfo.InvariantCulture) : 0;
@ -52,6 +57,128 @@ else
return 0;
static int AuditCellWindingCatalog(DatCollection dats)
{
long environments = 0;
long cellStructs = 0;
long polygons = 0;
long triangles = 0;
long degenerateTriangles = 0;
long alignedToPlane = 0;
long opposedToPlane = 0;
long ambiguousToNormal = 0;
long inconsistentFans = 0;
var sideCounts = new Dictionary<CullMode, long>();
var invalidSides = new List<string>();
var structuralFailures = new List<string>();
var windingObservations = new List<string>();
foreach (uint environmentId in dats.GetAllIdsOfType<DatEnvironment>().OrderBy(id => id))
{
DatEnvironment? environment = dats.Get<DatEnvironment>(environmentId);
if (environment is null)
continue;
environments++;
foreach (var (cellStructId, cellStruct) in environment.Cells.OrderBy(pair => pair.Key))
{
cellStructs++;
foreach (var (polygonId, polygon) in cellStruct.Polygons.OrderBy(pair => pair.Key))
{
if (polygon.VertexIds.Count < 3)
continue;
polygons++;
sideCounts[polygon.SidesType] = sideCounts.GetValueOrDefault(polygon.SidesType) + 1;
if (polygon.SidesType is not (CullMode.Landblock or CullMode.None or CullMode.Clockwise))
{
if (invalidSides.Count < 20)
invalidSides.Add($"env=0x{environmentId:X8} struct=0x{cellStructId:X4} poly=0x{polygonId:X4} sides={polygon.SidesType}");
}
if (!cellStruct.VertexArray.Vertices.TryGetValue(
(ushort)polygon.VertexIds[0],
out var anchorVertex))
{
if (structuralFailures.Count < 20)
structuralFailures.Add($"env=0x{environmentId:X8} struct=0x{cellStructId:X4} poly=0x{polygonId:X4} missing anchor");
continue;
}
Vector3 anchor = anchorVertex.Origin;
Vector3 authoredNormal = anchorVertex.Normal;
if (authoredNormal.LengthSquared() > 0f)
authoredNormal = Vector3.Normalize(authoredNormal);
int? fanSign = null;
for (int point = 2; point < polygon.VertexIds.Count; point++)
{
triangles++;
if (!TryGetOrigin(cellStruct, (ushort)polygon.VertexIds[point - 1], out Vector3 b)
|| !TryGetOrigin(cellStruct, (ushort)polygon.VertexIds[point], out Vector3 c))
{
if (structuralFailures.Count < 20)
structuralFailures.Add($"env=0x{environmentId:X8} struct=0x{cellStructId:X4} poly=0x{polygonId:X4} missing fan vertex");
continue;
}
Vector3 cross = Vector3.Cross(b - anchor, c - anchor);
if (cross.LengthSquared() <= 1e-12f || authoredNormal.LengthSquared() <= 0f)
{
degenerateTriangles++;
continue;
}
float dot = Vector3.Dot(Vector3.Normalize(cross), authoredNormal);
if (MathF.Abs(dot) < 1e-3f)
{
ambiguousToNormal++;
continue;
}
int sign = dot >= 0f ? 1 : -1;
if (fanSign is null)
fanSign = sign;
else if (fanSign.Value != sign)
{
inconsistentFans++;
if (windingObservations.Count < 20)
windingObservations.Add($"env=0x{environmentId:X8} struct=0x{cellStructId:X4} poly=0x{polygonId:X4} fan changes orientation dot={dot:R}");
}
if (sign > 0) alignedToPlane++;
else opposedToPlane++;
}
}
}
}
Console.WriteLine(
$"cell-winding-catalog environments={environments:N0} cellStructs={cellStructs:N0} " +
$"polygons={polygons:N0} triangles={triangles:N0} degenerate={degenerateTriangles:N0}");
Console.WriteLine(
$"orientation alignedToAuthoredNormal={alignedToPlane:N0} " +
$"opposedToAuthoredNormal={opposedToPlane:N0} ambiguous={ambiguousToNormal:N0} " +
$"inconsistentReliableFans={inconsistentFans:N0}");
Console.WriteLine(
"sides " + string.Join(", ", sideCounts.OrderBy(pair => (int)pair.Key)
.Select(pair => $"{pair.Key}({(int)pair.Key})={pair.Value:N0}")));
foreach (string failure in invalidSides)
Console.WriteLine($"INVALID-SIDES {failure}");
foreach (string failure in structuralFailures)
Console.WriteLine($"STRUCTURAL-FAIL {failure}");
foreach (string observation in windingObservations)
Console.WriteLine($"AUTHORED-FAN-OBSERVATION {observation}");
// Orientation observations describe the authored polygon fan. Retail
// submits the same [0,i-1,i] indices, so reproducing them is the parity
// contract; only missing vertices or an unknown sides_type are invalid.
return invalidSides.Count == 0 && structuralFailures.Count == 0
? 0
: 1;
}
static uint ParseHex(string text)
{
text = text.Trim();

View file

@ -74,6 +74,11 @@ foreach (var root in roots)
case SoundHook sh:
Console.WriteLine($"{head} sound=0x{(uint)sh.Id:X8}");
break;
case SoundTweakedHook tweaked:
Console.WriteLine(
$"{head} sound=0x{(uint)tweaked.SoundId:X8} " +
$"volume={tweaked.Volume:R} priority={tweaked.Priority:R}");
break;
case SoundTableHook stb:
Console.WriteLine($"{head} soundType={stb.SoundType}");
break;