fix #131 (root cause 4, structurally forced): look-in cells draw their DYNAMICS - the town portal is a server object in the hall's porch cell

The headless replay of the captured indoor frame proved the look-in flood ADMITS the porch 0x017A (Diagnostic_LookInFlood_AdmitsHallPorchFromCottage: 14 cells). So the portal (a SERVER object - the teleport proves it - with ParentCellId 0xA9B4017A) routes to partition.Dynamics and draws NOWHERE under an interior root: dynamics-last viewcone-culls it (the main cone has no look-in cells) and post-seal it would z-fail beyond the root's door plane (the #118 lesson). This is AP-33's own recorded deferral - 'look-in DYNAMICS are not drawn' - the deferred case was the most-stared-at object in town. Outdoors the merge path puts the porch in the main cone -> drawn -> 'appears when I walk out'.

Fix: DrawBuildingLookIns pass 2 draws look-in-cell dynamics with the statics (whole, AP-33 over-include) and their emitters ride the same DrawCellParticles call. No double-draw: dynamics-last keeps culling them; DrawDynamicsParticles only sees its cone survivors. #124 CLOSED by user gate same session. AP-33 row updated. Suites: App 261+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 20:52:34 +02:00
parent 47f32cd45c
commit d208002bf8
4 changed files with 89 additions and 27 deletions

View file

@ -353,19 +353,33 @@ public sealed class RetailPViewRenderer
_envCells.Render(WbRenderPass.Opaque, _oneCell);
_envCells.Render(WbRenderPass.Transparent, _oneCell);
if (partition.ByCell.TryGetValue(cellId, out var bucket) && bucket.Count > 0)
{
_cellStaticScratch.Clear();
_cellStaticScratch.Clear();
if (partition.ByCell.TryGetValue(cellId, out var bucket))
_cellStaticScratch.AddRange(bucket);
// #131 ROOT CAUSE: DYNAMICS living in a look-in cell (the
// Holtburg hall-porch PORTAL, pCell 0xA9B4017A) draw NOWHERE
// under an interior root — DrawDynamicsLast viewcone-culls
// them (the main cone has no entries for look-in cells), and
// post-clear they would z-fail against the root's seal anyway
// (the #118 lesson). Retail draws a look-in cell's objects
// inside the NESTED DrawCells (DrawObjCellForDummies,
// pc:432878+), i.e. right here in the landscape stage. Drawn
// WHOLE like the statics (AP-33's documented over-include).
// No double-draw: dynamics-last keeps culling them (their
// cell is absent from the main cone), and their emitters ride
// the DrawCellParticles call below, not DrawDynamicsParticles
// (which only sees dynamics-last cone survivors).
foreach (var e in partition.Dynamics)
if (e.ParentCellId == cellId)
_cellStaticScratch.Add(e);
if (_cellStaticScratch.Count > 0)
{
DrawEntityBucket(ctx, _cellStaticScratch, _oneCell);
// #131: the cell-particles pass for look-in cells — retail's
// nested DrawCells draws objects WITH their emitters
// (DrawObjCellForDummies, pc:432878+). Without this, an
// emitter owned by a far building's cell static (the
// Holtburg hall-porch portal swirl, cell 0x017A) drew ONLY
// when the viewer was outdoors (the merge path runs the
// main per-cell pass) — invisible from inside any cottage.
// The cell-particles pass for look-in cells — retail's
// nested DrawCells draws objects WITH their emitters.
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
ctx.DrawCellParticles?.Invoke(new RetailPViewCellSliceContext(
cellId, slice, _cellStaticScratch));