fix(render): Phase A8 — LiveDynamic in indoor branch + cull A/B gate

Two changes from visual-gate-#2 evidence.

LiveDynamic fix (real bug closure):
The user reported "can't see char ... door is missing" in visual gate #2.
Doors and the player char are LiveDynamic entities (ServerGuid != 0). The
outdoor branch's Draw(set: All) includes them; the indoor branch's
RenderInsideOutAcdream only renders IndoorPass + OutdoorScenery partitions,
implicitly excluding LiveDynamic. The method's own header comment promised
"LiveDynamic is drawn last in BOTH branches" but no call existed in the
indoor path — a documented behavior with no implementation. Wire the
LiveDynamic Draw after RenderInsideOutAcdream returns with stencil + state
restored to defaults at its cleanup block.

Cull A/B diagnostic (bisect floor-missing root cause):
ACDREAM_A8_DISABLE_CULL=1 forces every cell-mesh batch's effective CullMode
to None. The visual-gate-#2 audit confirmed cell meshes upload correctly
(every cell has multi-batch render data with non-zero indices, no null
data, no zero handles). Every batch uniformly reports CullMode.Landblock
which maps to glCullFace(Back) — identical to WB's mapping. So data is
fine and CullMode lookup is fine; only the BIND-TIME interaction (polygon
winding orientation in our coord system + cull-back) could still hide
specific polys. If floor appears with this gate set, cull/winding is the
remaining bug (need to either invert winding upstream or remap CullMode);
if not, the issue is elsewhere (lighting / depth / alpha) and we look
there. Tight bisect — one launch's evidence.

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

View file

@ -7361,6 +7361,22 @@ public sealed class GameWindow : IDisposable
camBuildings, otherBuildings,
camera, frustum, playerLb, animatedIds,
visibility?.VisibleCellIds);
// Phase A8 fix (2026-05-28 visual-gate-#2 follow-up): LiveDynamic
// entities (player char, NPCs, dropped items, doors) were missing
// inside buildings because RenderInsideOutAcdream only renders the
// IndoorPass + OutdoorScenery partitions; LiveDynamic was implicitly
// excluded. The method's own header comment promised "LiveDynamic
// is drawn last in BOTH branches" but no call existed in the indoor
// path. Wire it here, after RenderInsideOutAcdream returns with
// stencil + state restored to defaults at its cleanup block. Same
// shape as the outdoor branch's Draw(All) for the LiveDynamic
// subset only.
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
neverCullLandblockId: playerLb,
visibleCellIds: visibility?.VisibleCellIds,
animatedEntityIds: animatedIds,
set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.LiveDynamic);
}
else
{

View file

@ -81,6 +81,16 @@ public sealed unsafe class EnvCellRenderer : IDisposable
private static uint _currentVao;
private static CullMode? _currentCullMode;
// Phase A8 A/B diagnostic (2026-05-28 visual-gate-#2 follow-up):
// ACDREAM_A8_DISABLE_CULL=1 forces every batch's effective CullMode to
// None (no face culling). Used to isolate whether the missing-floor
// symptom is caused by polygon-winding+CullMode interaction or by
// something else (lighting, depth, alpha). If the floor appears with
// this set, cull/winding is the bug. If not, look elsewhere. Static
// because it's read once at startup; no need to re-query per draw.
private static readonly bool _forceCullModeNone =
string.Equals(Environment.GetEnvironmentVariable("ACDREAM_A8_DISABLE_CULL"), "1", StringComparison.Ordinal);
public bool NeedsPrepare { get; private set; } = true;
public bool IsDisposed { get; private set; }
@ -987,6 +997,10 @@ public sealed unsafe class EnvCellRenderer : IDisposable
foreach (var group in batchesByCullMode)
{
var cullMode = (CullMode)(group.Key % 4);
// A/B diagnostic: ACDREAM_A8_DISABLE_CULL=1 overrides every batch
// to no-culling. Reveals whether floor-missing is a cull/winding
// bug or something else.
if (_forceCullModeNone) cullMode = CullMode.None;
if (_currentCullMode != cullMode)
{
SetCullMode(cullMode);