refactor(render): Phase A8.F — Task 4 review follow-up (honest cap comment, cycle guard test, file fixpoint fast-follow)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-29 12:16:11 +02:00
parent 0ed462cb62
commit 270c21f263
3 changed files with 97 additions and 2 deletions

View file

@ -28,8 +28,16 @@ public sealed class PortalVisibilityFrame
public static class PortalVisibilityBuilder
{
// Bound on neighbour re-processing (retail uses update_count timestamps). Portal graphs are
// small; this guards cycles while allowing multi-portal unions into the same neighbour.
// Per-cell re-processing bound. NOTE: this cap is the actual termination mechanism for
// cyclic portal graphs, NOT merely a safety net — the re-enqueue-on-growth guard below is
// a near-no-op because CellView.Add never dedupes, so a cell almost always "grows". Retail
// instead converges via an update_count / set_view(...,i) slice watermark (decomp: AddToCell
// 433050 esi[0x11], InitCell timestamp, AddViewToPortals 433446). Consequences vs retail:
// (a) a cell reachable through >4 contributing portals under-counts; (b) duplicate polygons
// accumulate on cyclic/multi-path graphs (correctness survives — stencil marks are idempotent
// — but it is per-frame cost). Both bite only on dungeon-scale cyclic/hub graphs; a cottage
// cellar is a short chain where each cell is visited once. The faithful fixpoint port is filed
// as a fast-follow (docs/ISSUES.md) before A8.F is relied on for dungeons.
private const int MaxReprocessPerCell = 4;
private const float PortalSideEpsilon = 0.01f; // matches CellVisibility.PointInCellEpsilon
@ -47,6 +55,8 @@ public static class PortalVisibilityBuilder
var frame = new PortalVisibilityFrame();
if (cameraCell == null) return frame;
// Interior portals never cross landblocks (same invariant as CellVisibility.GetVisibleCells);
// building-boundary crossings are handled separately via the buildingMembership escape hatch.
uint lbMask = cameraCell.CellId & 0xFFFF0000u;
frame.CellViews[cameraCell.CellId] = CellView.FullScreen();