fix(render)+feat(diag): Phase A8 — sky-when-inside-building + per-cell audit probe

Two changes for visual-gate-#1 follow-up. After the pool aliasing fix
(prior commit), walls + objects render cleanly but three residual symptoms
remain: missing floor, purple wall tint, no sky through windows. This
commit addresses one and adds the probe for the second.

Sky fix:
The blanket `!cameraInsideCell` skip of the sky pass was inherited from
when the indoor-cell concept was sealed dungeons. With Phase A8's
RenderInsideOutAcdream pipeline, cottages render through their portals
to outside — and the user expects sky visible through windows + doorways.
WB's VisibilityManager.RenderInsideOut assumes sky has already been
rendered as the far-depth backdrop before stencil setup. New gate:
`!cameraInsideCell || cameraInsideBuilding`. Sky renders inside cottages
(building → portals), skipped inside true dungeons (no portals). The
Step 4 stencil-gated outdoor pass composites terrain + scenery through
portal silhouettes on top of the sky.

Per-cell audit probe (ACDREAM_A8_AUDIT=1):
One-shot dump per (cellId, gfxObjId) pair in the active snapshot:
- renderData null/non-null status
- batches count + total IndexCount
- per-batch CullMode + IsTransparent + IsAdditive + bindless-handle-zero
The first visual gate showed tris=135 for 18 cells — way too low if cell
meshes were complete (expected ~20+ tris/cell). The audit dump will
identify whether (a) some cells aren't uploading, (b) some batches have
zero indices, or (c) batches' CullModes are getting them culled at
typical viewing angles. Without this probe, we'd be back to speculation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-27 19:54:45 +02:00
parent 375f9a7b9b
commit 772d69c7a6
2 changed files with 81 additions and 3 deletions

View file

@ -103,6 +103,61 @@ public sealed unsafe class EnvCellRenderer : IDisposable
return (poolTotal, hwm);
}
/// <summary>
/// Phase A8 audit probe (2026-05-28 visual-gate-#1 follow-up).
/// One-shot per (cellId, gfxObjId) pair: dumps batch counts + CullModes +
/// transparency flags + bindless-handle-non-zero status, so the operator
/// can read offline and identify why specific polys (e.g., floors) aren't
/// rendering. Set <c>ACDREAM_A8_AUDIT=1</c> to enable.
/// <para>Returns a deduplicated audit-line list per Render snapshot
/// (one entry per (cellId, gfxObjId) seen in BatchedByCell). The caller
/// (GameWindow EmitEnvCellProbe) prints these and tracks which pairs
/// have already been logged.</para>
/// </summary>
public IReadOnlyList<string> CollectCellAuditLines(HashSet<(uint cellId, ulong gfxObjId)> alreadyLogged)
{
var lines = new List<string>();
lock (_renderLock)
{
var snap = _activeSnapshot;
foreach (var (cellId, gfxDict) in snap.BatchedByCell)
{
foreach (var (gfxObjId, transforms) in gfxDict)
{
var key = (cellId, gfxObjId);
if (alreadyLogged.Contains(key)) continue;
alreadyLogged.Add(key);
var rd = _meshManager.TryGetRenderData(gfxObjId);
if (rd is null)
{
lines.Add($"[a8-audit] cell=0x{cellId:X8} gfx=0x{gfxObjId:X10} instances={transforms.Count} renderData=null");
continue;
}
int totalIdx = 0;
var cullModes = new HashSet<DatReaderWriter.Enums.CullMode>();
int translucent = 0;
int additive = 0;
int zeroHandle = 0;
foreach (var b in rd.Batches)
{
totalIdx += b.IndexCount;
cullModes.Add(b.CullMode);
if (b.IsTransparent) translucent++;
if (b.IsAdditive) additive++;
if (b.BindlessTextureHandle == 0) zeroHandle++;
}
var cullList = string.Join(",", cullModes);
lines.Add(
$"[a8-audit] cell=0x{cellId:X8} gfx=0x{gfxObjId:X10} instances={transforms.Count} " +
$"isSetup={rd.IsSetup} batches={rd.Batches.Count} totalIdx={totalIdx} " +
$"cull=[{cullList}] translucent={translucent} additive={additive} zeroHandle={zeroHandle}");
}
}
}
return lines;
}
// ---------------------------------------------------------------------------
// Constructor + Initialize
// ---------------------------------------------------------------------------