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);
}
}