fix(#186): render side-cull mis-sided thin connectors — use dat PortalSide bit, not AABB centroid

The indoor GREY flap at a top-floor connecting room. The render portal side-cull
reconstructed each doorway's "interior side" (PortalClipPlane.InsideSide) from the
cell's AABB CENTROID. For a THIN connector cell (0xF6820118, 5 render polys), the
bounding-box center falls on the WRONG side of the 0118->0116 doorway, so the eye
read as a back-portal and the forward room 0116 was culled -> the aperture showed
the fog clear color = grey.

Retail's PView::InitCell (0x005a4b70) and acdream's own PHYSICS path
(CellTransit.cs:190) both read the explicit dat PortalSide bit ((Flags&2)==0)
instead of guessing from geometry. Port the render path (GameWindow.BuildLoadedCell)
to the same bit.

Proven by a live retail cdb trace (retail draws 0116 from the 0118 root at the grey
pose; tools/cdb/issue186-connector-decider.cdb) + an offline dat diagnostic
(Issue186...PortalSide_CentroidVsDatBit_AtGreyEye): the dat bit matches the old
centroid on every portal of these cells EXCEPT the one #186 breaks, so the switch is
surgical. Full regression green (App 741 / Core 2631); the CornerFlood + Issue113
dat-loading helpers updated to the same bit confirm every real Holtburg/tower/hall
cell floods identically. Touches neither PortalSideEpsilon nor the deleted
EyeInsidePortalOpening rescue (the two DO-NOT-RETRY traps).

Live-gated: user-confirmed no grey at any camera angle; probe shows 216 root=0118
frames, 0 still grey (0118->0116 now TRV, vis=4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-08 15:06:33 +02:00
parent 9d35a9786f
commit 8257b9ba10
6 changed files with 164 additions and 13 deletions

View file

@ -7390,9 +7390,6 @@ public sealed class GameWindow : IDisposable
var clipPlanes = new List<PortalClipPlane>();
var portalPolygons = new List<System.Numerics.Vector3[]>();
// Compute cell centroid in local space for InsideSide determination.
var centroid = (boundsMin + boundsMax) * 0.5f;
foreach (var portal in envCell.CellPortals)
{
portals.Add(new CellPortalInfo(
@ -7426,10 +7423,19 @@ public sealed class GameWindow : IDisposable
System.Numerics.Vector3.Cross(p1 - p0, p2 - p0));
float d = -System.Numerics.Vector3.Dot(normal, p0);
// Determine InsideSide: which side of the plane the cell centroid is on.
// If centroid dot > 0 → inside is positive half-space (InsideSide=0).
float centroidDot = System.Numerics.Vector3.Dot(normal, centroid) + d;
int insideSide = centroidDot >= 0 ? 0 : 1;
// InsideSide from the dat PortalSide bit — retail PView::InitCell
// (0x005a4b70) gates traversal on `iVar9 != portals->portal_side`, and
// acdream's own physics path uses the same bit (CellTransit.cs:190).
// The former AABB-centroid reconstruction mis-derived the interior side
// for THIN connector cells: the connector's bounding-box center falls on
// the wrong side of a portal near the cell edge, so the eye read as a
// back-portal and the forward room was culled — the #186 grey flap at
// 0xF6820118→0116 (retail draws 0116 from that root; cdb-confirmed).
// PortalSide (Flags&2)==0 → the portal polygon normal points INTO the
// owning cell → interior is the negative half → InsideSide=1. Diagnostic
// Issue186…PortalSide_CentroidVsDatBit_AtGreyEye: the dat bit matches the
// centroid on every portal of these cells EXCEPT the one #186 breaks.
int insideSide = ((ushort)portal.Flags & 0x2) == 0 ? 1 : 0;
clipPlanes.Add(new PortalClipPlane
{