using System.Numerics; namespace AcDream.App.Rendering.Walk; /// /// Campaign FW1 — retail PView's interior flood, transcribed from the /// flood-read appendix (docs/research/2026-08-30-fw-flood-pseudocode-appendix.md) /// plus the raw bodies re-read for AddToCell @0x005a4d90, AdjustDrawList /// @0x005a4e90, AdjustCellPlace @0x005a5010, FixCellList @0x005a5250, /// AdjustCellView @0x005a5770, and ConstructView @0x005a57b0. /// /// PORTAL-FLAG CONVENTION (resolved 2026-08-30 — the PDB names mislead): /// inflag (+4) == 1 marks a portal whose polygon FACES the viewer as /// a visible surface (it feeds max_indist); inflag == 0 with /// seen (+0) == 1 marks an OPENING on the viewer's look-through side /// (InitCell's side == portal_side arm — the same side condition /// ConstructView(CBldPortal) REQUIRES for building look-ins). /// ClipPortals' traversal gate seen != 0 && inflag != 1 /// therefore selects exactly the armed openings; the entry portal is forced /// {seen=1, inflag=1} so the flood never walks back out of it. Transcribe; /// do not "fix" the names. /// public sealed class WalkPView { private static int _masterTimestamp; private readonly struct TodoEntry(WalkCell cell, float dist) { public readonly WalkCell Cell = cell; public readonly float Dist = dist; } public readonly WalkPortalView OutsideView = new(); public readonly List CellDrawList = new(); private readonly List _todo = new(); private readonly WalkPortalView _otherPortalScratch = new(); // retail's static temp_view private Vector2[] _activeViewVerts = new Vector2[32]; private int _activeViewVertCount; private readonly WalkScreenPoint[] _projectScratch = new WalkScreenPoint[64]; private readonly WalkScreenPoint[] _clipScratch = new WalkScreenPoint[64]; /// Retail PView.draw_landscape: exit portals raise /// only when set. public bool DrawLandscape = true; /// Retail Render::PortalList as ClipPortals installs it — /// the last processed cell's top view (consumed by the landscape draw). public WalkPortalView? PortalList { get; private set; } // ------------------------------------------------------------------ // ConstructView (CEnvCell overload) @0x005a57b0 — the flood. // ------------------------------------------------------------------ public void ConstructView(WalkCell seed, int throughPortalIndex, IWalkFrameContext ctx) { OutsideView.ResetForPush(); // outside_view.view_count = 0 (counters only) _masterTimestamp++; _todo.Clear(); CellDrawList.Clear(); InitCell(seed, throughPortalIndex, ctx); InsCellTodoList(seed, 0f); while (_todo.Count > 0) { WalkCell cell = _todo[^1].Cell; _todo.RemoveAt(_todo.Count - 1); CellDrawList.Add(cell); cell.TopView.CellViewDone = true; if (ClipPortals(cell, 0, ctx)) AddViewToPortals(cell, ctx); } } // ------------------------------------------------------------------ // InitCell @0x005a4b70 (Ghidra-verified raw re-read 2026-08-30). // ------------------------------------------------------------------ public bool InitCell(WalkCell cell, int entryPortalIndex, IWalkFrameContext ctx) { WalkPortalView top = cell.TopView; if (top.ViewCount == 0) return false; Vector3 viewpoint = ctx.ViewpointIn(cell); top.CellViewDone = false; top.ViewTimestamp = _masterTimestamp; if (top.PortalFlags.Length < cell.Portals.Length) top.PortalFlags = new WalkPortalFlags[cell.Portals.Length]; float maxDistSquared = 0f; bool anyLookThrough = false; // retail var_4 (uninitialized there; benign — init false) for (int i = 0; i < cell.Portals.Length; i++) { ref WalkPortalFlags flags = ref top.PortalFlags[i]; WalkPolygon poly = cell.PortalPolygons[cell.Portals[i].PolygonIndex]; if (i == entryPortalIndex && !flags.InView) { // Entered-through portal: forced facing + armed — never re-traversed. flags.InView = true; flags.Seen = true; } else { flags.Seen = false; float d = Vector3.Dot(poly.Plane.Normal, viewpoint) + poly.Plane.D; if (d <= WalkVisibilityMath.Epsilon && d >= -WalkVisibilityMath.Epsilon) { flags.InView = false; // IN_PLANE: neither surface nor opening anyLookThrough = true; } else { int side = d > WalkVisibilityMath.Epsilon ? 0 : 1; if (side == cell.Portals[i].PortalSide) { flags.InView = false; // viewer on the look-through side (opening) anyLookThrough = true; } else { flags.InView = true; // portal polygon faces the viewer } } if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame) { Console.WriteLine( $"[walk-portal] init cell={cell.CellId:x8} i={i} " + $"dest={cell.Portals[i].OtherCellId:x8} d={d:F3} " + $"pside={cell.Portals[i].PortalSide} " + $"inview={(flags.InView ? 1 : 0)}"); } } if (flags.InView) { foreach (Vector3 v in poly.Vertices) { float dx = viewpoint.X - v.X; float dy = viewpoint.Y - v.Y; float dz = viewpoint.Z - v.Z; float d2 = dx * dx + dy * dy + dz * dz; if (d2 > maxDistSquared) maxDistSquared = d2; } } } top.MaxInDistSquared = maxDistSquared; // Fixup: arm every opening (retail runs this once per view with a // vestigial set_view per pass; the flag writes are idempotent). if (anyLookThrough && top.ViewCount > 0) { for (int j = 0; j < cell.Portals.Length; j++) { ref WalkPortalFlags flags = ref top.PortalFlags[j]; if (!flags.InView && !flags.Seen) flags.Seen = true; } } top.UpdateCount = top.ViewCount; return true; } // ------------------------------------------------------------------ // InsCellTodoList @0x005a4f50 — descending by dist from index 0; // END = nearest; pop-from-END = nearest-first (the Ghidra-corrected // polarity: the sink stops under the first STRICTLY greater key). // ------------------------------------------------------------------ public void InsCellTodoList(WalkCell cell, float dist) { int pos = _todo.Count; while (pos > 0 && !(dist < _todo[pos - 1].Dist)) pos--; _todo.Insert(pos, new TodoEntry(cell, dist)); } // ------------------------------------------------------------------ // ClipPortals @0x005a5520. // ------------------------------------------------------------------ public bool ClipPortals(WalkCell cell, int startView, IWalkFrameContext ctx) { WalkPortalView top = cell.TopView; PortalList = top; // set BEFORE any early-out (retail) if (cell.Portals.Length <= 0) return false; // Pass 1: which portals are live; resolve+cache neighbors. bool anyLive = false; for (int j = 0; j < cell.Portals.Length; j++) { ref WalkPortalFlags flags = ref top.PortalFlags[j]; if (!flags.Seen || flags.InView) continue; ref WalkCellPortal portal = ref cell.Portals[j]; if (cell.CachedNeighbors[j] is null && portal.OtherCellId != 0xFFFFFFFFu) { cell.CachedNeighbors[j] = ctx.GetVisible(portal.OtherCellId); if (cell.CachedNeighbors[j] is null) continue; // not loaded: silently dead } anyLive = true; } if (!anyLive) return false; // Pass 2: clip every live portal against each view in the window. for (int i = startView; i < top.ViewCount; i++) { SetView(top, i); for (int j = 0; j < cell.Portals.Length; j++) { ref WalkPortalFlags flags = ref top.PortalFlags[j]; if (!flags.Seen || flags.InView) continue; ref WalkCellPortal portal = ref cell.Portals[j]; int n = GetClip( cell, portal.PortalSide, cell.PortalPolygons[portal.PolygonIndex], doClip: true, ctx, _clipScratch); if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame) { Console.WriteLine( $"[walk-portal] clip cell={cell.CellId:x8} view={i} j={j} " + $"dest={portal.OtherCellId:x8} n={n}"); } if (n == 0) continue; if (portal.OtherCellId == 0xFFFFFFFFu) { if (DrawLandscape) { if (ctx.ClipLandscape) WalkCopyView.Append( OutsideView, _clipScratch.AsSpan(0, n), ctx.Rays, ctx.WorldViewpoint); else WalkCopyView.AppendFullViewportQuad( OutsideView, ctx.Rays, ctx.WorldViewpoint, ctx.ViewportWidth, ctx.ViewportHeight); } } else if (cell.CachedNeighbors[j] is WalkCell neighbor) { if (!portal.ExactMatch && portal.OtherPortalId >= 0) { n = OtherPortalClip(cell, j, n, ctx); SetView(top, i); // restore after the far-frame excursion if (n == 0) continue; } if (neighbor.NumView != 0) WalkCopyView.Append( neighbor.TopView, _clipScratch.AsSpan(0, n), ctx.Rays, ctx.WorldViewpoint); } } } return true; } // ------------------------------------------------------------------ // OtherPortalClip @0x005a5400 — the double clip for non-exact_match // portals: snapshot the near-clipped poly as a temp view, then re-clip // against the FAR cell's own portal polygon with INVERTED sidedness // (a literal == 0 test, not an XOR). // ------------------------------------------------------------------ private int OtherPortalClip( WalkCell cell, int portalIndex, int count, IWalkFrameContext ctx) { _otherPortalScratch.ResetForPush(); if (!WalkCopyView.Append( _otherPortalScratch, _clipScratch.AsSpan(0, count), ctx.Rays, ctx.WorldViewpoint)) return 0; ref WalkCellPortal portal = ref cell.Portals[portalIndex]; WalkCell far = cell.CachedNeighbors[portalIndex] ?? throw new InvalidOperationException( "OtherPortalClip requires a resolved neighbor (ClipPortals pass 1 contract)."); ref WalkCellPortal farPortal = ref far.Portals[portal.OtherPortalId]; SetView(_otherPortalScratch, 0); return GetClip( far, farPortal.PortalSide == 0 ? 1 : 0, far.PortalPolygons[farPortal.PolygonIndex], doClip: true, ctx, _clipScratch); } // ------------------------------------------------------------------ // AddViewToPortals @0x005a52d0. // ------------------------------------------------------------------ public void AddViewToPortals(WalkCell cell, IWalkFrameContext ctx) { for (int j = 0; j < cell.Portals.Length; j++) { ref WalkCellPortal portal = ref cell.Portals[j]; WalkCell? neighbor = cell.CachedNeighbors[j]; ref WalkPortalFlags flags = ref cell.TopView.PortalFlags[j]; if (neighbor is null || flags.InView || !flags.Seen || neighbor.NumView == 0) continue; WalkPortalView neighborTop = neighbor.TopView; if (neighborTop.ViewCount == 0) continue; if (neighborTop.UpdateCount == 0) { // First touch this flood: schedule the neighbor. if (InitCell(neighbor, ToEntryIndex(portal.OtherPortalId), ctx)) InsCellTodoList(neighbor, neighborTop.MaxInDistSquared); } else if (neighborTop.UpdateCount != neighborTop.ViewCount) { // Duplicate reach with NEW views since last processed. AddToCell(neighbor, ToEntryIndex(portal.OtherPortalId)); if (neighborTop.CellViewDone) FixCellList(neighbor, cell, ctx); neighborTop.UpdateCount = neighborTop.ViewCount; // fresh re-read after recursion } else { continue; // nothing new; NO SetOtherSeen either } if (portal.OtherPortalId >= 0) // full-width signed test (−1 sentinel skips) SetOtherSeen(cell, j); } } /// Retail passes other_portal_id as a ZERO-EXTENDED 16-bit read: /// −1 becomes 0xFFFF, InitCell/AddToCell's never-matching sentinel. private static int ToEntryIndex(int otherPortalId) => otherPortalId < 0 ? 0xFFFF : otherPortalId; // ------------------------------------------------------------------ // AddToCell @0x005a4d90 — the duplicate-reach flag refresh over the // NEW views window only. // ------------------------------------------------------------------ public void AddToCell(WalkCell cell, int entryPortalIndex) { WalkPortalView top = cell.TopView; for (int i = top.UpdateCount; i < top.ViewCount; i++) { // retail set_view(top.view, i) here is side-effect-only (vestigial) for (int j = 0; j < cell.Portals.Length; j++) { ref WalkPortalFlags flags = ref top.PortalFlags[j]; if (j == entryPortalIndex && !flags.InView) flags.InView = true; if (!flags.InView && !flags.Seen) flags.Seen = true; } } } // ------------------------------------------------------------------ // SetOtherSeen @0x005a4e30: fill the neighbor's backlink and arm its // back-portal when that portal faces the neighbor's viewer. // ------------------------------------------------------------------ public void SetOtherSeen(WalkCell cell, int portalIndex) { WalkCell? neighbor = cell.CachedNeighbors[portalIndex]; if (neighbor is null) return; int backIndex = cell.Portals[portalIndex].OtherPortalId; ref WalkCellPortal backPortal = ref neighbor.Portals[backIndex]; if (neighbor.CachedNeighbors[backIndex] is null) neighbor.CachedNeighbors[backIndex] = cell; ref WalkPortalFlags backFlags = ref neighbor.TopView.PortalFlags[backIndex]; if (backFlags.InView) backFlags.Seen = true; } // ------------------------------------------------------------------ // FixCellList @0x005a5250 = AdjustCellPlace + AdjustCellView. // ------------------------------------------------------------------ public void FixCellList(WalkCell moved, WalkCell reachedThrough, IWalkFrameContext ctx) { AdjustCellPlace(moved, reachedThrough); AdjustCellView(moved, ctx); } // AdjustCellPlace @0x005a5010: re-place, then recurse through the // reaching cell's armed facing portals. private void AdjustCellPlace(WalkCell moved, WalkCell reachedThrough) { WalkPortalView top = reachedThrough.TopView; if (!AdjustDrawList(moved, reachedThrough)) return; for (int i = 0; i < reachedThrough.Portals.Length; i++) { ref WalkPortalFlags flags = ref top.PortalFlags[i]; if (flags.Seen && flags.InView && reachedThrough.CachedNeighbors[i] is WalkCell next) AdjustCellPlace(reachedThrough, next); } } // AdjustDrawList @0x005a4e90: if `moved` appears in the draw list BEFORE // `reachedThrough`, insert `reachedThrough` at `moved`'s slot (shifting // the range up) so the walk-from-the-end draws `reachedThrough` earlier // (farther). Appends `reachedThrough` when absent. Returns true when a // move happened. private bool AdjustDrawList(WalkCell moved, WalkCell reachedThrough) { for (int i = 0; i < CellDrawList.Count; i++) { uint id = CellDrawList[i].CellId; if (id == reachedThrough.CellId) break; // already earlier: nothing to do if (id != moved.CellId) continue; int at = i; while (at < CellDrawList.Count && CellDrawList[at].CellId != reachedThrough.CellId) at++; if (at == CellDrawList.Count) CellDrawList.Add(reachedThrough); // conceptual append (grown below by the shift) for (int k = at; k > i; k--) CellDrawList[k] = CellDrawList[k - 1]; CellDrawList[i] = reachedThrough; return true; } return false; } // AdjustCellView @0x005a5770: incremental re-flood over the new-views // window only (the update_count watermark). private void AdjustCellView(WalkCell cell, IWalkFrameContext ctx) { if (ClipPortals(cell, cell.TopView.UpdateCount, ctx)) AddViewToPortals(cell, ctx); } // ------------------------------------------------------------------ // set_view @0x0054d0e0 (the slice GetClip consumes: the installed // view's screen vertices). // ------------------------------------------------------------------ public void SetView(WalkPortalView portalView, int polyIndex) { WalkViewPoly poly = portalView.View.Polys[polyIndex]; if (_activeViewVerts.Length < poly.VertexCount) _activeViewVerts = new Vector2[poly.VertexCount]; for (int k = 0; k < poly.VertexCount; k++) _activeViewVerts[k] = portalView.View.Vertices[poly.VertexIndex + k].Point; _activeViewVertCount = poly.VertexCount; } // ------------------------------------------------------------------ // PView::GetClip @0x005a4320: project, order by sidedness, optionally // clip against the installed view. Returns the surviving count (0 on // full rejection — the caller's pre-zero contract). // ------------------------------------------------------------------ public int GetClip( WalkCell cell, int side, WalkPolygon polygon, bool doClip, IWalkFrameContext ctx, Span output) { int n = polygon.Vertices.Length; Matrix4x4 objectToClip = ctx.ObjectToClip(cell); for (int i = 0; i < n; i++) { _projectScratch[i] = WalkScreenClip.TransformToScreen( polygon.Vertices[i], objectToClip, ctx.ViewportWidth, ctx.ViewportHeight); } // Sidedness != POSITIVE reverses the winding (IN_PLANE takes the // reversed branch too, though flood callers never pass it). if (side != 0) { for (int i = 0; i < n / 2; i++) (_projectScratch[i], _projectScratch[n - 1 - i]) = (_projectScratch[n - 1 - i], _projectScratch[i]); } if (!doClip) { _projectScratch.AsSpan(0, n).CopyTo(output); return n; } return WalkScreenClip.ClipAgainstView( _projectScratch.AsSpan(0, n), _activeViewVerts.AsSpan(0, _activeViewVertCount), output); } }