docs(render): Phase U.2a review fixups — LIFO-on-ties comment + ISSUES #102

Code-review minor follow-ups: correct the CellTodoList comments (ties are LIFO,
not FIFO — an equal-distance newcomer lands at the tail and pops first, matching
retail's break-on-first-not-greater + pop-from-tail). Update ISSUES #102 to record
that U.2a closes I-1/I-2 (under-count + duplicate accumulation) via the enqueue-once
gate, narrowing the residual to diamond-topology clip-completeness (AddToCell onward
re-propagation, tracked under U.6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-30 16:30:41 +02:00
parent d8807755ce
commit 306cdb069c
2 changed files with 27 additions and 4 deletions

View file

@ -84,11 +84,32 @@ Related: #102 (builder dungeon-scaling fixpoint).
## #102 — A8.F PortalVisibilityBuilder — port retail update_count fixpoint (replace MaxReprocessPerCell cap) ## #102 — A8.F PortalVisibilityBuilder — port retail update_count fixpoint (replace MaxReprocessPerCell cap)
**Status:** OPEN **Status:** PARTIALLY RESOLVED (Phase U.2a, 2026-05-30, commit `d880775`)
**Severity:** MEDIUM **Severity:** MEDIUM → LOW (residual is diamond-topology clip-completeness only)
**Filed:** 2026-05-29 **Filed:** 2026-05-29
**Component:** rendering, visibility, EnvCell portal traversal **Component:** rendering, visibility, EnvCell portal traversal
**U.2a resolution (2026-05-30):** Reading the decomp showed retail does NOT
re-enqueue on view-growth: `AddViewToPortals` (433446) enqueues a cell via
`InsCellTodoList` ONLY in the first-discovery branch (`ecx_5 == 0`); later
growth goes through `AddToCell` (433050) in place and never re-enqueues. U.2a
replaced the `MaxReprocessPerCell` cap with an **enqueue-once gate** (a `seen`
set = retail `cell_view_done`, 433784) + a distance-priority work list (retail
`InsCellTodoList`). This **closes I-1 and I-2**: the clip-region union into a
neighbour now runs UNCONDITIONALLY before the enqueue gate, so >4-portal cells
no longer under-count (I-1 gone), and each cell processes its exit portals
exactly once, so cyclic graphs no longer accumulate duplicate polygons (I-2
gone). The new `Build_CyclicHub_TerminatesAndBounds` test enforces the
acceptance (4-room ring ⇒ ≤5 cells, no dups). **Residual scope:** retail's
`AddToCell` ONWARD re-propagation of late growth (a cell reached via a longer
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.
**Description:** A8.F Task 4 shipped a bounded-BFS port of retail's **Description:** A8.F Task 4 shipped a bounded-BFS port of retail's
`PView::ConstructView``ClipPortals``AddViewToPortals` in `PView::ConstructView``ClipPortals``AddViewToPortals` in
[`src/AcDream.App/Rendering/PortalVisibilityBuilder.cs`](../src/AcDream.App/Rendering/PortalVisibilityBuilder.cs). [`src/AcDream.App/Rendering/PortalVisibilityBuilder.cs`](../src/AcDream.App/Rendering/PortalVisibilityBuilder.cs).

View file

@ -268,7 +268,8 @@ public static class PortalVisibilityBuilder
/// the tail; <see cref="PopNearest"/> removes the tail — giving closest-first traversal exactly /// the tail; <see cref="PopNearest"/> removes the tail — giving closest-first traversal exactly
/// as ConstructView's pop-from-(cell_todo_num-1) does (433767-433769). The insertion only shifts /// as ConstructView's pop-from-(cell_todo_num-1) does (433767-433769). The insertion only shifts
/// entries strictly farther than the newcomer (retail's flag test breaks on the first /// entries strictly farther than the newcomer (retail's flag test breaks on the first
/// not-greater entry), so ties preserve insertion order (stable, FIFO among equal distances). /// not-greater entry), so an equal-distance newcomer lands at the tail and pops FIRST —
/// LIFO on ties, matching retail's break-on-first-not-greater + pop-from-tail.
/// </summary> /// </summary>
private sealed class CellTodoList private sealed class CellTodoList
{ {
@ -280,7 +281,8 @@ public static class PortalVisibilityBuilder
{ {
// Find the slot: scan from the tail (nearest) toward the head while existing entries are // Find the slot: scan from the tail (nearest) toward the head while existing entries are
// strictly nearer than `distance`, so the newcomer lands just ABOVE every entry that is // strictly nearer than `distance`, so the newcomer lands just ABOVE every entry that is
// farther-or-equal — i.e. nearest-at-tail order, FIFO on ties. // farther-or-equal — i.e. nearest-at-tail order, LIFO on ties (an equal-distance
// newcomer inserts at the tail and pops first).
int idx = _items.Count; int idx = _items.Count;
while (idx > 0 && _items[idx - 1].Distance < distance) while (idx > 0 && _items[idx - 1].Distance < distance)
idx--; idx--;