research(render): Phase U.4c-1 — characterize the flap on real dat evidence

A8CellAudit portals dump extended to print per-portal plane + centroid-derived
InsideSide vs the dat's authored PortalSide. Real Holtburg cottage cells show:
the flap is a DIRECT 0xA9B40171->0xA9B40170 portal side-test flip (0170 is a
direct neighbour, not multi-hop), and our centroid-derived InsideSide is
anti-correlated with the dat PortalSide that retail InitCell (432896) uses.
Evidence selects H2 (port the side test) over H1 (PVS set-grounding). Camera
cell 0171 seenOutside=Y. Full reading + fix direction + open sign question in
the note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-31 10:13:16 +02:00
parent 639f20fa8a
commit 13d58cae6a
2 changed files with 127 additions and 0 deletions

View file

@ -189,9 +189,46 @@ static void DumpCellPortals(DatCollection dats, uint envCellId)
(missing.Count > 0 ? $" MISSING=[{string.Join(",", missing)}]" : "");
}
// U.4c characterization: compute the plane + centroid-derived InsideSide
// EXACTLY as GameWindow.BuildLoadedCell does, and compare its traversal
// SENSE against the dat's authored PortalSide flag (retail portal_side).
// If they disagree for a portal the camera should see through, that is the
// flap's root: our centroid guess culls where retail's authored bit traverses.
string sideText = "no-cellStruct";
if (cellStruct is not null && cellStruct.Polygons.TryGetValue(portal.PolygonId, out var sp)
&& sp.VertexIds.Count >= 3
&& TryGetOrigin(cellStruct, (ushort)sp.VertexIds[0], out var s0)
&& TryGetOrigin(cellStruct, (ushort)sp.VertexIds[1], out var s1)
&& TryGetOrigin(cellStruct, (ushort)sp.VertexIds[2], out var s2))
{
var n = Vector3.Cross(s1 - s0, s2 - s0);
n = n.LengthSquared() > 0f ? Vector3.Normalize(n) : Vector3.Zero;
float d = -Vector3.Dot(n, s0);
// Cell centroid = AABB center over all cellStruct verts (matches BuildLoadedCell).
var mn = new Vector3(float.MaxValue); var mx = new Vector3(float.MinValue);
foreach (var v in cellStruct.VertexArray.Vertices.Values)
{ mn = Vector3.Min(mn, v.Origin); mx = Vector3.Max(mx, v.Origin); }
var centroid = (mn + mx) * 0.5f;
float centroidDot = Vector3.Dot(n, centroid) + d;
int ourInsideSide = centroidDot >= 0 ? 0 : 1; // GameWindow.cs:5648
int datPortalSide = portal.Flags.HasFlag(PortalFlags.PortalSide) ? 1 : 0;
// Our test traverses when the camera is on the centroid's side of the plane.
// Retail (InitCell 432962) traverses when computed-sidedness != portal_side,
// where sidedness==1 means "in front" (dot>eps). At the centroid our test
// always traverses; the meaningful question is whether the centroid side
// (front if centroidDot>0) matches retail's traverse-sense for this portal.
int centroidSidedness = centroidDot > 0 ? 1 : 0; // 1=front
bool retailTraversesAtCentroid = centroidSidedness != datPortalSide;
sideText =
$"N=({n.X:F2},{n.Y:F2},{n.Z:F2}) d={d:F2} centroidDot={centroidDot:F3} " +
$"ourInsideSide={ourInsideSide} datPortalSide={datPortalSide} " +
$"retailTraversesAtCentroid={(retailTraversesAtCentroid ? "Y" : "n")}";
}
Console.WriteLine(
$" portal[{i}] other=0x{portal.OtherCellId:X4} -> {dest} " +
$"flags={portal.Flags} polyId={portal.PolygonId} | {resolveText}");
Console.WriteLine($" SIDES: {sideText}");
}
Console.WriteLine(