feat(render): Phase U.2b — reciprocal OtherPortalClip (retail 433524)

Clip the portal opening against the neighbour's matching back-portal polygon
before propagating, so a cell's clip region is the intersection of the opening
seen from both sides. Closes the M-4 stub in ISSUES #102. Can only tighten,
never under-include; degrades to prior behavior when no back-portal is found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-30 16:37:14 +02:00
parent 306cdb069c
commit 3916b2b23e
3 changed files with 194 additions and 14 deletions

View file

@ -105,10 +105,12 @@ acceptance (4-room ring ⇒ ≤5 cells, no dups). **Residual scope:** retail's
path AFTER it was drawn gets its own `CellView` unioned but does not
re-propagate that growth to ITS children) is NOT ported — this affects only
clip-region completeness on **diamond** topologies, never the visible cell set
or draw order. It overlaps the M-4 `OtherPortalClip` stub below; track both
under U.6 (dungeon-scale validation). A naive count-watermark re-enqueue is NOT
a valid fix (it never terminates, because `CellView.Add` appends without
merging) — the faithful fix is the in-place slice re-propagation.
or draw order. Track under U.6 (dungeon-scale validation). (The M-4
`OtherPortalClip` stub noted below is now CLOSED by Phase U.2b — a separate
concern from this onward-re-propagation gap.) A naive count-watermark
re-enqueue is NOT a valid fix (it never terminates, because `CellView.Add`
appends without merging) — the faithful fix is the in-place slice
re-propagation.
**Description:** A8.F Task 4 shipped a bounded-BFS port of retail's
`PView::ConstructView``ClipPortals``AddViewToPortals` in
@ -144,13 +146,21 @@ Retail anchors (`docs/research/named-retail/acclient_2013_pseudo_c.txt`):
- `InitCell` — per-cell timestamp init
- `AddViewToPortals` 433446 — change-detection that drives the fixpoint
**Related M-4 stub:** the neighbour-side `OtherPortalClip`
(decomp:433524) is also not yet ported — the builder clips the portal
opening against the *current* cell's view but does NOT additionally clip
against the *neighbour's* matching portal polygon. Its absence can only
ever OVER-include (mark cells/regions visible that retail would cull),
never under-include, so it's deferred. There is a `TODO(A8.F)` marker at
the relevant site in `PortalVisibilityBuilder.cs`.
**Related M-4 stub — CLOSED (Phase U.2b, 2026-05-30):** the
neighbour-side `OtherPortalClip` (decomp:433524) is now ported. After a
portal's near-side opening is clipped against the current cell's view,
`PortalVisibilityBuilder.ApplyReciprocalClip` resolves the neighbour's
matching back-portal (scan `neighbour.Portals` for the entry whose
`OtherCellId` == the near cell's low-16-bits id; `PortalPolygons` is in
lockstep), projects it through the neighbour's `WorldTransform`, and
intersects it into the propagated region before the union — so a cell's
clip region is the intersection of the opening seen from BOTH sides. Can
only TIGHTEN; degrades to prior near-side-only behavior when no
back-portal is found. The `TODO(A8.F)` marker is removed. Covered by
`PortalVisibilityBuilderTests.Build_AppliesReciprocalOtherPortalClip`
(reciprocal tightening) + `…_DegradesGracefully_WhenNoBackPortal`.
(The diamond-topology onward re-propagation of late growth remains out of
scope here — tracked under U.6.)
**Files:**
- `src/AcDream.App/Rendering/PortalVisibilityBuilder.cs` — replace the