file #131 (portal swirl gone through doorways) + #132 (candle flame vs aperture background) + the [outstage] capture probe

Two user reports from the #124 gate session, both axioms:
- #131: "the portal swirl is missing, when I look out from inside a
  house. Appears when I walk out again." Mechanism frame: under an
  interior root an outdoor dynamic's particles draw ONLY via the
  landscape slice's Scene pass (#118 outside-stage routing; #121
  excludes them from the last-pass particle callback) - if any link
  fails, the swirl draws nowhere exactly when indoors. Desk-exonerated
  already: filter key conventions uniform, the routing predicate
  correct, sphere from vertex bounds.
- #132: "I have a candle ... when a wall is behind it it shows, but if
  I turn a bit and the opening through a house is behind it candle
  light disappears." Background-dependent => per-pixel depth/blend at
  the aperture region, not owner culling. Possible overlap with the
  #124 look-in sub-pass (new pre-clear content in those pixels) - the
  pre-77cef4c check is in the issue.

Apparatus (env-gated, zero cost off): ACDREAM_PROBE_OUTSTAGE=1 ->
[outstage] per-slice outside-stage routing + cone verdict per dynamic
(print-on-change, RetailPViewRenderer) + [outstage-pt] slice
Scene-particle id set + live attached-emitter match count (GameWindow).
One capture standing inside looking at the portal pins which link
breaks.

Suites: App 259+1skip / Core 1439+2skip / UI 420 / Net 294 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-12 18:54:56 +02:00
parent 77cef4cd86
commit eeb1c59ded
4 changed files with 127 additions and 0 deletions

View file

@ -4581,6 +4581,67 @@ or distance.
---
## #131 — Portal swirl invisible when viewed from inside a building through the doorway
**Status:** OPEN
**Severity:** MEDIUM (portals are landmark objects; the through-door view is common)
**Filed:** 2026-06-12 (user report, #124 gate session)
**Component:** render — outside-stage dynamics' particles under interior roots (#118/#121 family)
**Symptom (user, axiom):** "the portal swirl is missing, when I look out
from inside a house. Appears when I walk out again."
**Mechanism frame:** under an interior root an outdoor dynamic routes to
the OUTSIDE stage (`_outsideStageDynamics`, #118) and its particles'
ONLY path is the landscape slice's Scene pass
(`_outdoorSceneParticleEntityIds`); the last-pass particle callback
deliberately excludes outside-stage entities (#121: "already drew in
the slice"). If any link fails (slice cone verdict, the id set, emitter
matching, draw order vs the slice's blend state), the swirl draws
NOWHERE exactly when indoors — and reappears outdoors where
DrawDynamicsLast + DrawDynamicsParticles take over. Matches the report
exactly.
**Desk-exonerated (2026-06-12):** key conventions are uniform
(`ParticleEntityKey` = ServerGuid-first at all three filter sites);
`DynamicDrawsInOutsideStage` routes outdoor dynamics correctly;
`EntitySphere` uses the vertex-derived bounds.
**Apparatus (shipped, env-gated):** `ACDREAM_PROBE_OUTSTAGE=1`
`[outstage]` (per-slice routing + cone verdict per outside-stage
dynamic, print-on-change) + `[outstage-pt]` (slice Scene-particle id
set + live attached-emitter matched count). Capture: stand inside,
look at the portal through the door.
---
## #132 — Candle flame disappears when the through-opening background is behind it
**Status:** OPEN
**Severity:** LOW-MEDIUM
**Filed:** 2026-06-12 (user report, #124 gate session)
**Component:** render — cell-particle compositing vs aperture pixels
**Symptom (user, axiom):** "I have a candle, when I look at the candle
when a wall is behind it it shows, but if I turn a bit and the opening
through a house is behind it candle light disappears."
**Reading:** BACKGROUND-dependent disappearance — the candle (and its
owner static) stays in view; only what is behind it changes. That rules
out viewcone/owner culling (which keys on the candle's own position)
and points at per-pixel state in the aperture region: depth left by the
punch/seal/look-in machinery at those pixels, draw order of the cell
particle pass vs the aperture passes, or blend state. Candidate overlap
with the #124 look-in sub-pass (new pre-clear content in exactly those
pixels) — check whether the symptom predates `77cef4c` by looking at a
candle in front of a doorway WITHOUT a through-house view.
**Next:** repro at the spot + `ACDREAM_PROBE_OUTSTAGE` lines for the
same frame; then a depth-state walkthrough of the aperture pixels for
the cell-particle pass.
---
# Recently closed
## #113 — Phantom staircase: REOPENED 2026-06-11, folded into the HOLISTIC BUILDING-RENDER PORT

View file

@ -5094,6 +5094,9 @@ public sealed class GameWindow : IDisposable
private static uint ParticleEntityKey(AcDream.Core.World.WorldEntity entity)
=> entity.ServerGuid != 0 ? entity.ServerGuid : entity.Id;
// #131 [outstage-pt] probe state (throwaway — strip when #131 closes).
private string? _lastOutStagePtSig;
private static System.Numerics.Vector3 SkyPesAnchor(
AcDream.Core.World.SkyObjectData obj,
System.Numerics.Vector3 cameraWorldPos)
@ -9638,6 +9641,27 @@ public sealed class GameWindow : IDisposable
foreach (var entity in sliceCtx.OutdoorEntities)
_outdoorSceneParticleEntityIds.Add(ParticleEntityKey(entity));
// #131 [outstage-pt] probe: the slice Scene-particle id set + how many
// live emitters the filter would actually match. Print-on-change.
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeOutStageEnabled
&& _particleSystem is not null)
{
int matched = 0, attached = 0;
foreach (var (emitter, _) in _particleSystem.EnumerateLive())
{
if (emitter.AttachedObjectId == 0) continue;
attached++;
if (_outdoorSceneParticleEntityIds.Contains(emitter.AttachedObjectId)) matched++;
}
string ptSig = System.FormattableString.Invariant(
$"ids={_outdoorSceneParticleEntityIds.Count} attachedEmitters={attached} matched={matched}");
if (ptSig != _lastOutStagePtSig)
{
_lastOutStagePtSig = ptSig;
Console.WriteLine("[outstage-pt] " + ptSig);
}
}
DisableClipDistances();
if (_outdoorSceneParticleEntityIds.Count > 0
&& _particleSystem is not null

View file

@ -397,6 +397,8 @@ public sealed class RetailPViewRenderer
if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
_outdoorStaticScratch.Add(e);
}
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeOutStageEnabled)
EmitOutStageProbe(probeSliceIndex, viewcone);
probeSliceIndex++;
ctx.DrawLandscapeSlice(new RetailPViewLandscapeSliceContext(slice, _outdoorStaticScratch));
}
@ -420,6 +422,32 @@ public sealed class RetailPViewRenderer
UseIndoorMembershipOnlyRouting();
}
// #131 [outstage] probe state (2026-06-12, throwaway): print-on-change —
// which outdoor dynamics were routed to the outside stage and which
// survived the slice viewcone. Strip with the probe when #131 closes.
private string? _lastOutStageSig;
private void EmitOutStageProbe(int sliceIndex, ViewconeCuller viewcone)
{
var sb = new System.Text.StringBuilder(192);
sb.Append("slice=").Append(sliceIndex)
.Append(" outStage=").Append(_outsideStageDynamics.Count).Append(" [");
for (int i = 0; i < _outsideStageDynamics.Count; i++)
{
var e = _outsideStageDynamics[i];
EntitySphere(e, out var c, out float r);
bool pass = viewcone.SphereVisibleInOutsideSlice(sliceIndex, c, r);
if (i > 0) sb.Append(' ');
sb.Append(System.FormattableString.Invariant(
$"0x{(e.ServerGuid != 0 ? e.ServerGuid : e.Id):X8}:{(pass ? "PASS" : "CULL")}:r={r:F1}"));
}
sb.Append(']');
string sig = sb.ToString();
if (sig == _lastOutStageSig) return;
_lastOutStageSig = sig;
Console.WriteLine("[outstage] " + sig);
}
// §4 flap [clip-route] probe state (2026-06-10, throwaway): print-on-change signature +
// monotonic sequence so held-flap vs healthy frames diff cleanly in one capture.
private string? _lastClipRouteSig;

View file

@ -109,6 +109,20 @@ public static class RenderingDiagnostics
public static bool ProbeViewerEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIEWER") == "1";
/// <summary>
/// #131 (2026-06-12) outside-stage dynamics probe. When true, the renderer
/// emits one <c>[outstage]</c> line per CHANGE of the outside-stage
/// routing + per-slice cone verdict set under an interior root (which
/// outdoor dynamics were routed to the landscape slice, which survived the
/// slice viewcone), and GameWindow emits one <c>[outstage-pt]</c> line per
/// change of the slice Scene-particle id set + matched-emitter count.
/// Built for the portal-swirl-missing-through-doorway capture. Light:
/// silent while the set is stable. Initial state from
/// <c>ACDREAM_PROBE_OUTSTAGE=1</c>.
/// </summary>
public static bool ProbeOutStageEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_OUTSTAGE") == "1";
/// <summary>
/// Phase U.4c (2026-05-31) flap-convergence probe. When true, the portal
/// visibility pass emits, EVERY frame the camera root is an indoor cell, a