The indoor doorway flap is the portal flood's re-enqueue churn (0171<->0173 mutual re-contribution; drifted near-duplicate regions AddRegion won't dedup -> grew -> re-enqueue, capped at MaxReprocessPerCell=16 -> eye-sensitive flood depth -> grey flash). Confirmed live: launch-churn-confirm.log shows maxPop=16 on 44% of frames during a doorway walk-through. The 2026-06-08 'maxPop=1, churn refuted' verdict was a camera-turn-at-rest capture (wrong reproduction); its DO-NOT is overturned. Fix (Option A, user-approved): contributions already covered by the neighbour's accumulated view don't grow it (no re-enqueue); only the uncovered remainder propagates -- retail's 'redundant -> empty before copy_view' (copy_view confirmed to just append). Remove MaxReprocessPerCell; keep re-processing of genuinely-new slices. Scope: PortalVisibilityBuilder only. Revives 2026-06-08 spec+plan (banners redirected). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
✅ REVIVED 2026-06-09. Phase 1 (the churn probe) is done; Phase 2 (port the bound) is now the active R-A2b work — design:
../specs/2026-06-09-portal-flood-bounded-propagation-r-a2b-design.md. The⛔banner below was wrong: themaxPop=1reading came from a camera-turn-at-rest capture (the calm position, root0172), the wrong reproduction. A 2026-06-09 doorway walk-through capture (launch-churn-confirm.log, the proper Phase-1 Task-4 pin) measuredmaxPop=16on 44 % of frames → churn confirmed. Task 4's prediction ("redundant reciprocal back-contribution stays non-empty") is confirmed byrecip=1->1, grew=True.
⛔ (HISTORICAL — corrected above) SUPERSEDED 2026-06-08 (evening). Live
ACDREAM_PROBE_PORTAL_CHURNmeasuredmaxPop=1— but on the wrong reproduction (camera turn at rest), not a doorway crossing (see the revival note above). The Phase-1 churn probe this plan added is correct and is the tool that ultimately confirmed (not disproved) the churn once aimed at the actual flap.
Portal-Flood Bounded-Propagation Port Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Eliminate the indoor doorway "flap" by porting retail's bounded portal-flood propagation into PortalVisibilityBuilder — keeping re-processing on growth (retail-faithful) but stopping the reciprocal/drift churn that makes membership eye-sensitive.
Architecture: The flap is PortalVisibilityBuilder.Build's unbounded re-enqueue churn (cs:322, MaxReprocessPerCell=16 hack): redundant reciprocal back-contributions yield drifted non-empty slivers → grew → re-enqueue, and the churn's fixpoint shifts under sub-cm eye motion. Retail bounds it structurally (monotonic update_count watermark + empty reciprocal). Phase 1 instruments + pins the exact acdream divergence at the live doorway (it's float-drift-dependent — a runtime fact, not derivable from decomp). Phase 2 ports the bound, gated on Phase 1's evidence. Spec: docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md (REVISION banner).
Tech Stack: C# / .NET 10, xUnit. GL-free unit tests (AcDream.App.Tests/Rendering/PortalVisibilityBuilderTests.cs). Live capture via dotnet run against local ACE.
Non-goals (unchanged from spec): no rooting/camera/clip-math-rewrite/seal change; physics + the 4 rest-stability regression tests stay; Build_ViewGrowthAfterDoneCell stays GREEN (re-processing is kept).
PHASE 1 — Instrument & pin the exact divergence
Task 1: Add the portal-churn probe flag
Files:
-
Modify:
src/AcDream.Core/Rendering/RenderingDiagnostics.cs(afterProbePvInputEnabled, ~line 144) -
Test:
tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs -
Step 1: Write the failing test
[Fact]
public void ProbePortalChurn_DefaultsFalse_WhenEnvUnset()
{
// Env var is absent in the test host, so the flag must default false (inert probe).
Assert.False(AcDream.Core.Rendering.RenderingDiagnostics.ProbePortalChurnEnabled);
}
- Step 2: Run it — expect FAIL (compile error:
ProbePortalChurnEnableddoes not exist)
Run: dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~ProbePortalChurn_DefaultsFalse"
Expected: FAIL (does not compile).
- Step 3: Add the flag (in
RenderingDiagnostics, mirroringProbePvInputEnabled)
/// <summary>
/// Bounded-propagation port apparatus (2026-06-08). When true, PortalVisibilityBuilder.Build emits
/// one [portal-churn] summary line per call: per-cell pop count (re-pops = churn), total re-enqueues,
/// max pop count, and — per re-enqueue — the reciprocal-clip pre→post region count + grew flag. Pins
/// whether the flap's churn is redundant reciprocal back-contributions producing non-empty drifted
/// slivers (the hypothesis) vs another source. Throwaway apparatus — strip once the bound ships.
/// Initial state from ACDREAM_PROBE_PORTAL_CHURN=1.
/// </summary>
public static bool ProbePortalChurnEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_PORTAL_CHURN") == "1";
-
Step 4: Run it — expect PASS. Run the same filter. Expected: PASS.
-
Step 5: Commit
git add src/AcDream.Core/Rendering/RenderingDiagnostics.cs tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs
git commit -m "feat(render-diag): add ACDREAM_PROBE_PORTAL_CHURN flag for the bounded-propagation pin"
Task 2: Instrument PortalVisibilityBuilder.Build churn + reciprocal
Files:
-
Modify:
src/AcDream.App/Rendering/PortalVisibilityBuilder.cs(the pop loop ~145-178; the reciprocal site ~295-306; the re-enqueue ~322; beforereturn frame~341) -
Step 1: Add a per-Build churn accumulator (top of
Build, aftervar popCounts = ...~line 112)
// [portal-churn] apparatus (2026-06-08): when ProbePortalChurnEnabled, accumulate re-enqueue churn
// + reciprocal pre/post region counts, emitted as one summary line at end of Build. Inert when off.
bool churnProbe = AcDream.Core.Rendering.RenderingDiagnostics.ProbePortalChurnEnabled;
int churnReenqueues = 0;
var churnReciprocal = churnProbe ? new System.Text.StringBuilder(256) : null;
- Step 2: Record reciprocal pre→post (at the reciprocal site, right after
ApplyReciprocalClip(...)~line 297, before theif (clippedRegion.Count == 0)check)
if (churnProbe)
churnReciprocal!.Append(System.FormattableString.Invariant(
$" recip[0x{neighbourId:X8} {preReciprocalCount}->{clippedRegion.Count}]"));
- Step 3: Count re-enqueues (inside the
if (grew && ... queued.Add(neighbourId))block ~line 322, aftertodo.Insert(neighbour, dist))
if (churnProbe) churnReenqueues++;
- Step 4: Emit the summary (just before
return frame;~line 341)
if (churnProbe)
{
int maxPop = 0; uint maxCell = 0; int rePopped = 0;
foreach (var kv in popCounts)
{
if (kv.Value > maxPop) { maxPop = kv.Value; maxCell = kv.Key; }
if (kv.Value > 1) rePopped++;
}
Console.WriteLine(System.FormattableString.Invariant(
$"[portal-churn] root=0x{cameraCell.CellId:X8} cells={frame.OrderedVisibleCells.Count} " +
$"reEnqueues={churnReenqueues} rePoppedCells={rePopped} maxPop=0x{maxCell:X8}:{maxPop}" +
churnReciprocal));
}
-
Step 5: Build — Run:
dotnet build src/AcDream.App/AcDream.App.csproj -c Debug. Expected:Build succeeded. -
Step 6: Commit
git add src/AcDream.App/Rendering/PortalVisibilityBuilder.cs
git commit -m "diag(render): [portal-churn] probe — per-Build re-enqueue + reciprocal pre/post"
Task 3: Deterministic re-pop unit test (probe baseline)
Files:
-
Modify:
tests/AcDream.App.Tests/Rendering/PortalVisibilityBuilderTests.cs -
Step 1: Write the test — the
ViewGrowthAfterDoneCelltopology re-popsB(legitimate late growth fromD). This proves the re-pop path is exercised deterministically (so the probe + later fix have a non-flaky anchor) without needing live float-drift.
[Fact]
public void Build_ViewGrowthAfterDoneCell_RePopsGrownCell()
{
// Same A->B(near LEFT) + A->D(far RIGHT) + D->B(later) topology as
// Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit: B is popped via LEFT, then D grows B
// through RIGHT after B is done -> B re-pops. This is retail-faithful late growth (kept by the fix).
const uint A = 0x0001, B = 0x0002, D = 0x0003;
var a = Cell(A, new CellPortalInfo((ushort)B, 0, 0, 0), new CellPortalInfo((ushort)D, 1, 0, 0));
a.PortalPolygons.Add(QuadX(-0.9f, -0.3f, -2f));
a.PortalPolygons.Add(QuadX(0.3f, 0.9f, -5f));
var b = Cell(B, new CellPortalInfo((ushort)A, 0, 0, 0),
new CellPortalInfo(0xFFFF, 1, 0, 0), new CellPortalInfo((ushort)D, 2, 0, 0));
b.PortalPolygons.Add(QuadX(-0.9f, -0.3f, -2f));
b.PortalPolygons.Add(QuadX(0.3f, 0.9f, -3f));
b.PortalPolygons.Add(QuadX(0.3f, 0.9f, -5f));
var d = Cell(D, new CellPortalInfo((ushort)B, 0, 0, 2));
d.PortalPolygons.Add(QuadX(0.3f, 0.9f, -5f));
var all = new Dictionary<uint, LoadedCell> { [A] = a, [B] = b, [D] = d };
var frame = Build(a, all);
// Membership (the flap-relevant output) is each cell once, regardless of re-pops.
Assert.Equal(new[] { B }, frame.OrderedVisibleCells.Where(c => c == B).ToArray());
Assert.Contains(D, frame.OrderedVisibleCells);
}
- Step 2: Run it — expect PASS (documents current behavior; the re-pop happens internally, membership stays deduped).
Run: dotnet test tests/AcDream.App.Tests/AcDream.App.Tests.csproj --filter "FullyQualifiedName~Build_ViewGrowthAfterDoneCell_RePopsGrownCell"
Expected: PASS.
- Step 3: Commit
git add tests/AcDream.App.Tests/Rendering/PortalVisibilityBuilderTests.cs
git commit -m "test(render): deterministic re-pop anchor for the bounded-propagation pin"
Task 4: Live capture at the doorway + pin (CHECKPOINT — needs the user)
- Step 1: Launch with the churn probe (add
$env:ACDREAM_PROBE_PORTAL_CHURN = "1"to a copy oflaunch-flap-capture.ps1; keepACDREAM_PROBE_PVINPUT=1for correlation).dotnet buildgreen first, then launch in background, tee toflap-churn.log. - Step 2: User reproduces — stand at the cottage doorway, turn the camera back and forth (the flap). ~15 s.
- Step 3: Analyze
flap-churn.log: for the flap frames (correlate with[pv-input]flood flips), inspect[portal-churn]: which cells hit highmaxPop(churn → near 16), and therecip[...]pre→post counts — is a redundant reciprocal back-contribution staying non-empty (pre->postboth >0 on a cell that already contributed)? That is the predicted divergence. - Step 4: Pin + write the Phase 2 fix plan. Record the pinned divergence (the exact cell/portal/condition where the redundant contribution stays non-empty) in a short findings note, and write
docs/superpowers/plans/2026-06-08-portal-flood-bound-fix.md(Phase 2) with the exact, evidence-grounded code change. Do not write Phase 2 code before this pin.
PHASE 2 — Port the bound (outline; finalized from Phase 1's pin)
Shape (locked; exact predicate from Task 4): make redundant reciprocal / re-clip contributions not generate a new propagatable slice — matching retail's empty-reciprocal (OtherPortalClip→no copy_view) + monotonic update_count watermark — then remove MaxReprocessPerCell + popCounts (termination becomes structural). Keep re-processing on growth (the AddRegion union + the deferred re-process), so Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit stays GREEN. Most-likely concrete form (confirm in Task 4): gate the grew → re-enqueue so a back-contribution whose clipped region is already covered by the cell's accumulated view (retail: empty after reciprocal clip) does not count as growth — via the faithful reciprocal/watermark, NOT an epsilon dedup heuristic.
Tests: the new eye-sweep stability test (membership a single contiguous run as the eye sweeps — synthetic grazing topology if reproducible, else the live [pv-input] gate); all existing PortalVisibilityBuilderTests green incl. Build_ViewGrowthAfterDoneCell_*, Build_IsDeterministic_*, Builder_CyclicGraph/Hub termination; the 4 physics rest-stability guards green.
Acceptance (visual gate — the real one): at the cottage doorway, turn the camera back and forth and walk through — interior rooms render steadily, no battling/popping; [pv-input] flood stable per eye pose; [portal-churn] maxPop ≤ small constant (no near-16 churn). Then strip the apparatus ([portal-churn], [pv-input], the launch scripts).
Self-Review notes
- Spec coverage: Phase 1 implements the spec's "instrument + pin first" requirement; Phase 2 implements the bounded-propagation fix.
Build_ViewGrowthAfterDoneCellexplicitly kept green (spec correction). No rooting/camera/clip/seal change. - Phase 2 is intentionally an outline: its exact predicate is the runtime fact Phase 1 pins (the spec + this plan both flag this). Phase 2 gets its own no-placeholder plan after Task 4 — this is the apparatus-first discipline, not a deferred placeholder.