#121: dynamics-owner particle pass - world portals visible again; re-gate ledger in ISSUES

Fix: dynamics' ATTACHED emitters (portal swirls on server-spawned portal
entities, creature effects) fell through EVERY particle filter under the
unified pview path - the landscape slice filter carries outdoor statics
(+ the #118 outside-stage dynamics), the per-cell callback carries cell
statics, and T4 deleted the clipRoot==null global pass from normal
frames. T5 never checked portals; the user's re-gate caught it ("all
portals that were previously showing are now gone"). DrawDynamicsLast
now hands its cone-surviving dynamics (minus outside-stage entities,
whose emitters already drew in the landscape slice - alpha particles
must not double-draw) to a new DrawDynamicsParticles callback;
GameWindow draws Scene-pass emitters filtered to those owner ids,
mirroring DrawRetailPViewCellParticles. Retail shape: emitters draw
with their owner object.

Re-gate ledger (user verdicts are axioms):
- #117 CLOSED ("Yes solved"), #118 CLOSED ("Yes solved" + NPC-through-
  door "Yes fixed").
- #108 REOPENED narrowed: cellar-ascent eye-below-grade window only
  (grass covers the exit door until the head pops over ground level);
  fix belongs on the membership/viewer side - the depth-gated punch
  stays (DO-NOT-RETRY).
- #119 user split: phantom walkable stairs at the hill cottage (#113
  family), tower missing stairs + barrel (#119 proper), hill-house
  transparent-on-entry (#112 - re-check after the #120 fix; the
  ping-pong fired at exactly A9B3 0103/010F).
- #120 FIXED pending re-gate (dede7e4).
- NEW #122 window oscillation on entry (re-check after #120 first),
  NEW #123 buildings transiently disappear running close past,
  NEW #124 far-building back walls missing through openings (lead:
  per-building look-in floods run only for outdoor roots -
  NearbyBuildingCells is null for interior roots; retail runs the
  look-in inside LScape::draw for ANY root).

Suites: App 236, Core 1419+2skip, UI 420, Net 294.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-11 17:36:58 +02:00
parent dede7e491c
commit c4464739d2
3 changed files with 220 additions and 12 deletions

View file

@ -3701,14 +3701,25 @@ Unverified. The likely culprits, ranked by suspected probability:
---
## #108 — Cellar↔main-floor transition: terrain (grass) sweeps across the upstairs door opening — [DONE 2026-06-11 · T5 gate]
## #108 — Cellar↔main-floor transition: terrain (grass) sweeps across the upstairs door opening — [REOPENED 2026-06-11 · narrowed residual]
**Status:** DONE — user-confirmed GONE at the T5 comprehensive gate
("3. Check" — cellar descent + ascent with the upstairs doorway watched
during the transition). Closed by the holistic-port stack: the T1 retail
frame order + T2 flood fidelity + the BR-7 membership/collision work
(the punch that previously masked-then-exposed it was rebuilt as part of
the discipline). No isolated fix commit — the discipline retired the class.
**Status:** REOPENED (narrowed) — the broad symptom is GONE (T5 +
re-gate #2: "Yes, but…"), but a residual remains in ONE window: during
the cellar ASCENT, while the eye is still below ground level, the
upstairs exit-door opening is covered with grass — "like the ground
level rose to the top of the door … as soon as my head pops up it falls
back to ground level" (user, re-gate 2026-06-11). The original
BR-2-era diagnosis stands: grass-sweep frames render through the
OUTDOOR root (membership/viewer-cell flips outdoor mid-cellar), and the
#117 depth-gated punch then correctly refuses to punch the aperture
where terrain depth is NEARER than the door fan (eye below grade ⇒ the
visible front-facing terrain can sit between the eye and the door in
depth). The punch must STAY depth-gated (DO-NOT-RETRY) — the fix is on
the membership/viewer side (why is the root outdoor while the eye is in
the cellar stairwell below grade?). Apparatus shape: a vertical
cellar-ascent variant of the #118 exit-walk harness (drive the eye up
the stair path; log root resolution + the punch's mark-pass outcome per
step). Prior history below.
**Severity:** MEDIUM
**Component:** ~~render / indoor PView~~**physics / membership** (cellar-transition root flip)
@ -3756,7 +3767,13 @@ stable now; this is a draw-order/depth oscillation localized to the door surface
## #112 — A9B3 hill cottage: containment gap inside the house demotes to outdoor with no re-promotion (transparent interior while walking)
**Status:** OPEN
**Status:** OPEN — user re-confirmed at the 2026-06-11 re-gate: "that
hill house also sometimes when entering, all walls turn transparent.
Only that house." NOTE: the #120 reciprocal ping-pong fired at exactly
A9B3 `0103↔010F` during that session — the runaway duplicate views are a
plausible alternate mechanism for the transparent frames. Re-check this
symptom AFTER the #120 fix (`dede7e4`) before resuming the membership
investigation.
**Severity:** MEDIUM-HIGH (any house with interior containment gaps; user-observed
"sometimes transparent" while walking around inside)
**Filed:** 2026-06-10 (late — user exploration after #111 closed)
@ -3910,7 +3927,7 @@ ad hoc — the DO-NOT-RETRY table's slide entries (physics digest) apply.
---
## #117 — Aperture-shaped see-through: doors/interiors visible through terrain hills and through nearer buildings
## #117 — Aperture-shaped see-through: doors/interiors visible through terrain hills and through nearer buildings — [DONE 2026-06-11 · 478c549, user re-gate "Yes solved"]
**Status:** OPEN
**Severity:** HIGH (the most visible remaining render artifact post-port)
@ -3935,9 +3952,11 @@ AFTER the punch. Compare against the T1 (`579c8b0`) punch pass wiring.
---
## #118 — Character clipped + disappears for a moment when exiting houses
## #118 — Character clipped + disappears for a moment when exiting houses — [DONE 2026-06-11 · 5a80a2e, user re-gate "Yes solved"]
**Status:** FIXED 2026-06-11 — pending visual re-gate
**Status:** DONE — user-confirmed at the 2026-06-11 re-gate ("Yes
solved"), including the outdoor-NPC-through-doorway companion symptom
("Yes fixed").
**Root cause (pinned by the exit-walk harness, `HouseExitWalkReplayTests`):**
NOT the cone stack — candidates 13 all exonerated (cone-level walk passes
@ -4046,15 +4065,43 @@ location) — then the cell set + flood can be replayed headlessly like
#118. The extraneous water barrel remains a separate static-inclusion
question (which cell owns it; is it admitted by a view it shouldn't be).
**User split (re-gate 2026-06-11) — THREE distinct artifacts in the
area:**
1. The PHANTOM walkable-but-invisible stairs (the #113 family) is still
present and now reads as located at the HILL COTTAGE — "the stairs
half embedded into the outside wall." (#113's reopened
drawing-BSP-orphan investigation owns this.)
2. A tower CLOSE TO the hill cottage has the MISSING stairs + the
extraneous water barrel in its middle — this entry (#119) proper.
3. The hill house sometimes turns ALL walls transparent when entering —
tracked under #112; note the #120 ping-pong fired at exactly A9B3
0103↔010F, so re-check after the #120 fix (`dede7e4`).
---
## #120 — [pv-ERROR] in-place propagation tripwire: convergence invariant broken at depth 128 (cottage interior cells)
**Status:** OPEN
**Status:** FIXED 2026-06-11 (`dede7e4`) — pending re-gate (watch for
zero `[pv-ERROR]` lines in the next launch log)
**Severity:** HIGH (self-detected invariant break in the new flood growth)
**Filed:** 2026-06-11 (T5 launch log; fired during normal cottage play)
**Component:** render — PortalVisibilityBuilder in-place growth (T2/BR-4)
**RESOLVED (2026-06-11):** the armed tripwire self-attributed on the
re-gate launch — a pure TWO-CELL reciprocal ping-pong (`0xA9B4015C ↔
0x0162` and `0xA9B30103 ↔ 0x010F`, 64 laps each). Mechanism: eye within
PortalSideEpsilon (±1 cm) of the portal plane → in-plane counts interior
for BOTH cells → views lap A→B→A; near-edge-on aperture re-clips wobble
beyond the 1e-3 dedup grid → every lap keys "new". The prior sweeps
couldn't reproduce because they only loaded the corner building — both
firing pairs are outside it. `Issue120ReciprocalPingPongTests` loads the
full landblock and reproduces deterministically (tripwire firings +
65-polygon CellView piles). Fix: `CellView.Add` rejects polygons
CONTAINED in an already-stored polygon (a round-trip re-emission is a
subset of its originator in exact math) — union growth is strictly
area-increasing, the lap dies at iteration 1. Corner-flood completeness
pins stay green. PortalSideEpsilon untouched (DO-NOT-RETRY).
**Evidence:** `[pv-ERROR] in-place propagation tripwire at depth 128 on
cell=0xA9B40175 / 0xA9B40174 / 0xA9B40162 — convergence invariant broken,
investigate` (3+ firings in the T5 session, exactly the cottage interior
@ -4085,6 +4132,99 @@ Revisit on the next firing (the #117/#118 re-gate launch will carry it).
---
## #121 — All world portals invisible (portal swirl VFX gone everywhere)
**Status:** FIXED 2026-06-11 — pending re-gate
**Severity:** HIGH (user: "all portals that were previously showing at
various places are now gone")
**Filed:** 2026-06-11 (re-gate launch)
**Component:** render — particle pass routing under the pview path
**Root cause (by read):** dynamics' ATTACHED emitters (portal swirls on
server-spawned portal entities, creature effects) fell through EVERY
particle filter under the unified pview path: the landscape slice's
filter carries outdoor STATICS (+ the #118 outside-stage dynamics), the
per-cell callback carries cell STATICS, and T4 deleted the old
`clipRoot==null` global pass from normal frames. T5 never checked
portals (not on the checklist) — the gap dates to the T3/T4 one-gate
work, surfaced at this re-gate. **Fix:** a dynamics-owner particle pass
`DrawDynamicsLast` hands its cone-surviving dynamics (minus
outside-stage entities, whose emitters already drew in the landscape
slice) to a new `DrawDynamicsParticles` callback; GameWindow draws
Scene-pass emitters filtered to those owner ids (mirror of
`DrawRetailPViewCellParticles`). Retail shape: emitters draw with their
owner object.
---
## #122 — Windows oscillate between background and the correct outside view when entering houses
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-06-11 (re-gate; user: "the oscillating between
background world and the right outside view is now back on some windows
when entering houses")
**Component:** render — window exit-portal region at the root flip
The #109 oscillation family, now localized to WINDOWS during house
ENTRY (the outdoor→interior root flip). Candidate mechanisms:
(a) the #120 reciprocal ping-pong polluting clip volumes near the
portal plane during the crossing — the firing sites were exactly
cottage cells during entry; RE-CHECK after `dede7e4` before
investigating; (b) the seal/punch handoff on windows across the root
flip (forceFarZ keys on `clipRoot.IsOutdoorNode`, flipping the window
aperture between punch and seal semantics frame-to-frame at the
threshold).
---
## #123 — Buildings transiently disappear when running close past them
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-06-11 (re-gate; user: "when I pass by close by
buildings, sometimes the building disappears as I run by")
**Component:** render — outdoor root, close-range building draw
Whole-building transient vanish at close range under the outdoor root.
Suspects (unverified): the per-building frustum pre-gate on
`Building.PortalBounds` (T2 draw-driven flood gather) interacting with
close-range AABB degeneracy; dispatcher frustum cull with a stale
entity AABB; or the #117 stencil punch marking a near-full-screen
aperture fan at grazing range while the building's own flood is gated
off (far-Z holes → sky/fog where the shell should be). Needs evidence
first: reproduce with `ACDREAM_PROBE_VIS`/`[outdoor-node]` + a capture
of which draw list the building's shell left.
---
## #124 — Looking out through an opening: far buildings with openings show missing/transparent back walls
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-06-11 (re-gate; pre-existing — "still have that issue")
**Component:** render — per-building look-in floods under INTERIOR roots
From inside a building, looking out through a door/window at ANOTHER
building that has an opening: the far building's back walls are
missing/transparent (see the world through it). **Lead (by read):** the
per-building look-in floods (`MergeNearbyBuildingFloods`) run ONLY for
outdoor roots — `RetailPViewDrawContext.NearbyBuildingCells` is
documented "Null for interior roots." So under an interior root the far
building's INTERIOR never floods: through its window you see the shell
only, and a shell has no interior back-wall faces → transparent.
Retail runs the building look-in inside `LScape::draw` (DrawBlock →
DrawPortal → ConstructView(CBldPortal)), which executes for ANY root
whose outside view is non-empty — including interior roots looking out
a doorway. Fix shape: provide the nearby-building gather + per-building
floods for interior roots too, with look-in apertures getting PUNCH
semantics (the `forceFarZ` selector currently keys on
`clipRoot.IsOutdoorNode`, which under-punches this case). Needs its own
focused pass — touches the gather, the merge, and the depth-mask
selector.
---
# Recently closed
## #113 — Phantom staircase: REOPENED 2026-06-11, folded into the HOLISTIC BUILDING-RENDER PORT

View file

@ -7729,6 +7729,8 @@ public sealed class GameWindow : IDisposable
forceFarZ: clipRoot.IsOutdoorNode),
DrawCellParticles = sliceCtx =>
DrawRetailPViewCellParticles(sliceCtx, camera, camPos),
DrawDynamicsParticles = survivors =>
DrawRetailPViewDynamicsParticles(survivors, camera, camPos),
EmitDiagnostics = result =>
EmitRetailPViewDiagnostics(
result,
@ -9630,6 +9632,43 @@ public sealed class GameWindow : IDisposable
DisableClipDistances();
}
// #121: the dynamics-owner particle pass — Scene-pass emitters attached to
// the frame's cone-surviving dynamics (portal swirls on server-spawned
// portal entities, creature effects). Retail draws emitters with their
// owner object; before this pass, dynamics' emitters fell through every
// pview particle filter (landscape slice = outdoor statics + #118
// outside-stage dynamics; cell callback = cell statics) once T4 deleted
// the clipRoot==null global pass from normal frames — every world portal
// went invisible. Mirror of DrawRetailPViewCellParticles with the
// survivors' ids as the filter.
private readonly HashSet<uint> _dynamicsSceneParticleEntityIds = new();
private void DrawRetailPViewDynamicsParticles(
IReadOnlyList<AcDream.Core.World.WorldEntity> survivors,
ICamera camera,
System.Numerics.Vector3 camPos)
{
if (_particleSystem is null || _particleRenderer is null || survivors.Count == 0)
return;
_dynamicsSceneParticleEntityIds.Clear();
foreach (var entity in survivors)
_dynamicsSceneParticleEntityIds.Add(ParticleEntityKey(entity));
if (_dynamicsSceneParticleEntityIds.Count == 0)
return;
DisableClipDistances();
_particleRenderer.Draw(
_particleSystem,
camera,
camPos,
AcDream.Core.Vfx.ParticleRenderPass.Scene,
emitter => emitter.AttachedObjectId != 0
&& _dynamicsSceneParticleEntityIds.Contains(emitter.AttachedObjectId));
DisableClipDistances();
}
private void EmitRetailPViewDiagnostics(
AcDream.App.Rendering.RetailPViewFrameResult result,
LoadedCell clipRoot,

View file

@ -445,6 +445,26 @@ public sealed class RetailPViewRenderer
UseIndoorMembershipOnlyRouting();
DrawEntityBucket(ctx, _dynamicsScratch, visibleCellIds: null);
// #121: dynamics' attached emitters (portal swirls, creature effects)
// gate through the SAME cone-surviving owner set as their meshes —
// retail draws emitters with the owner object. Before this callback,
// dynamics' emitters fell through EVERY particle filter under the pview
// path (the landscape slice carries outdoor statics + #118 outside-
// stage dynamics; the cell callback carries cell statics; T4 deleted
// the old clipRoot==null global pass from normal frames) — all world
// portals went invisible. Outside-stage dynamics are excluded here:
// their emitters already drew in the landscape slice (alpha-blended
// particles must not double-draw, unlike the depth-idempotent meshes).
if (ctx.DrawDynamicsParticles is not null)
{
_dynamicsParticleScratch.Clear();
foreach (var e in _dynamicsScratch)
if (!_outsideStageDynamics.Contains(e))
_dynamicsParticleScratch.Add(e);
if (_dynamicsParticleScratch.Count > 0)
ctx.DrawDynamicsParticles(_dynamicsParticleScratch);
}
}
private void DrawCellObjectLists(
@ -509,6 +529,9 @@ public sealed class RetailPViewRenderer
// #118: dynamics assigned to the OUTSIDE stage this frame (interior roots
// only) — outdoor-classified + exit-portal straddlers. Cleared per frame.
private readonly List<WorldEntity> _outsideStageDynamics = new();
// #121: cone-surviving dynamics whose emitters draw in the dynamics
// particle pass (survivors minus outside-stage). Cleared per use.
private readonly List<WorldEntity> _dynamicsParticleScratch = new();
/// <summary>
/// #118 stage assignment for a dynamic under an INTERIOR root: does it draw
@ -650,6 +673,11 @@ public interface IRetailPViewCellDrawContext : IRetailPViewCellDrawCallbacks
public FrustumPlanes? Frustum { get; }
public uint? PlayerLandblockId { get; }
public HashSet<uint>? AnimatedEntityIds { get; }
/// <summary>#121: draw the Scene-pass emitters attached to the frame's
/// cone-surviving dynamics (portal swirls, creature effects). Invoked once
/// per frame after the last entity pass with the survivor list.</summary>
public Action<IReadOnlyList<WorldEntity>>? DrawDynamicsParticles { get; }
}
public sealed class RetailPViewDrawContext : IRetailPViewCellDrawContext
@ -683,6 +711,7 @@ public sealed class RetailPViewDrawContext : IRetailPViewCellDrawContext
public Action? ClearDepthForInterior { get; init; }
public Action<RetailPViewCellSliceContext>? DrawExitPortalMasks { get; init; }
public Action<RetailPViewCellSliceContext>? DrawCellParticles { get; init; }
public Action<IReadOnlyList<WorldEntity>>? DrawDynamicsParticles { get; init; }
public Action<RetailPViewFrameResult>? EmitDiagnostics { get; init; }
}