fix(render): branch inside/outside on is_player_outside, not the camera cell (PARTIAL)
Retail SmartBox::RenderNormalMode (0x453aa0:92665) branches DrawInside vs the outdoor LScape::draw on is_player_outside (the PLAYER's cell, 0x451e80), then roots DrawInside at the VIEWER cell. acdream keyed the whole branch off the camera cell (clipRoot = visibility.CameraCell), so a 3rd-person chase camera lagging in a doorway AFTER the player stepped outside took the DrawInside path rooted at the threshold cell, where the exit-portal flood degenerates: grey world + entities-through-walls. Now ShouldRenderIndoor(playerCellId, viewerCellResolved) gates the branch on the player; the DrawInside root stays the viewer cell (handoff invariant preserved). SCOPE / HONESTY: this REDUCES the player-OUTSIDE doorway grey (visual-confirmed reduced a lot) but does NOT fix the deeper symptom: when the player is in one interior cell (cellar 0174) and the camera is in another (room 0171), the flood roots at the camera cell and does NOT seal the player's cell, so the cellar floor / interior walls drop to grey. That is the KNOWN R1-completion problem (2026-06-05 Residual A handoff + 2026-06-02 design doc section 3: a SHELL-SEALING / wrong-flood-root bug), not this branch. Tests: Core 1331p / 4f (documented) / 1s, App 187p, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d2212cfaea
commit
2b7f5a16c6
3 changed files with 81 additions and 1 deletions
|
|
@ -7319,7 +7319,19 @@ public sealed class GameWindow : IDisposable
|
|||
// (mesh SSBO) + binding=2 (terrain UBO); each renderer re-binds its
|
||||
// binding=2 defensively from the ids we hand it.
|
||||
_clipFrame ??= ClipFrame.NoClip();
|
||||
var clipRoot = visibility?.CameraCell;
|
||||
// Retail RenderNormalMode (0x453aa0:92665) branches inside/outside on is_player_outside
|
||||
// — the PLAYER's cell (0x451e80), NOT the camera cell — then roots DrawInside at the
|
||||
// VIEWER cell (this->viewer_cell) when inside. The 3rd-person chase camera LAGS the
|
||||
// player, so keying the branch off the camera (the old `visibility?.CameraCell`) made
|
||||
// the camera lingering in a doorway AFTER the player had stepped outside take the
|
||||
// DrawInside path rooted at the threshold cell, where the exit-portal flood degenerates
|
||||
// → terrain Skipped + sparse shells → grey world with only entities showing through.
|
||||
// Branch on the player; keep the viewer cell as the indoor root (handoff invariant).
|
||||
uint playerCellId = _physicsEngine.DataCache?.CellGraph.CurrCell?.Id ?? 0u;
|
||||
var clipRoot = AcDream.Core.Rendering.RenderingDiagnostics.ShouldRenderIndoor(
|
||||
playerCellId, visibility?.CameraCell is not null)
|
||||
? visibility!.CameraCell
|
||||
: null;
|
||||
ClipFrameAssembly? clipAssembly = null;
|
||||
PortalVisibilityFrame? pvFrame = null; // R1: hoisted so the binary decision below reads OrderedVisibleCells
|
||||
var terrainClipMode = TerrainClipMode.Planes; // overwritten below for indoor root
|
||||
|
|
|
|||
|
|
@ -272,4 +272,29 @@ public static class RenderingDiagnostics
|
|||
/// in the 8×8 landblock grid (0x0001–0x0040).
|
||||
/// </summary>
|
||||
public static bool IsEnvCellId(ulong id) => (id & 0xFFFFu) >= 0x0100u;
|
||||
|
||||
/// <summary>
|
||||
/// The top-level render branch: should this frame run the indoor (DrawInside) path?
|
||||
///
|
||||
/// <para>Retail <c>SmartBox::RenderNormalMode</c> (0x453aa0, pc:92665) branches
|
||||
/// DrawInside vs the outdoor <c>LScape::draw</c> on <c>is_player_outside</c> — the
|
||||
/// <b>PLAYER's</b> cell (<c>(player->m_position.objcell_id & 0xFFFF) < 0x100</c>,
|
||||
/// <c>SmartBox::is_player_outside</c> 0x451e80) — NOT the camera/viewer cell. When the
|
||||
/// player is inside it then roots the flood at the <b>viewer</b> cell
|
||||
/// (<c>this->viewer_cell</c>). So the inside/outside <i>decision</i> follows the player;
|
||||
/// only the indoor <i>root</i> follows the camera.</para>
|
||||
///
|
||||
/// <para>acdream historically branched on the camera cell (a non-null
|
||||
/// <c>visibility.CameraCell</c>). A 3rd-person chase camera lags the player, so when the
|
||||
/// player had already stepped outside but the camera still sat in the doorway, the camera
|
||||
/// branch wrongly chose DrawInside rooted at the doorway cell, where the exit-portal flood
|
||||
/// degenerates → the whole static world (terrain + shells) gated off → grey screen with
|
||||
/// only entities (which bypass the gate) showing through. Branching on the player removes it.</para>
|
||||
///
|
||||
/// <param name="playerCellId">The player's current cell id (0 if unresolved → outside).</param>
|
||||
/// <param name="viewerCellResolved">Whether a viewer/camera cell is available to root
|
||||
/// DrawInside at. Indoor render needs both: the player inside AND a cell to root at.</param>
|
||||
/// </summary>
|
||||
public static bool ShouldRenderIndoor(uint playerCellId, bool viewerCellResolved)
|
||||
=> viewerCellResolved && IsEnvCellId(playerCellId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,4 +89,47 @@ public sealed class RenderingDiagnosticsTests
|
|||
{
|
||||
Assert.Equal(expected, RenderingDiagnostics.IsEnvCellId(id));
|
||||
}
|
||||
|
||||
// ── Render inside/outside branch (retail RenderNormalMode is_player_outside) ──
|
||||
// The top-level render branch decides DrawInside vs DrawOutside. Retail
|
||||
// (SmartBox::RenderNormalMode 0x453aa0:92665) keys it on is_player_outside (the
|
||||
// PLAYER's cell, 0x451e80), NOT the camera cell. acdream previously branched on the
|
||||
// camera cell, so a chase camera lagging in a doorway while the player was already
|
||||
// outside took the DrawInside path and degenerated to a grey world + entities showing
|
||||
// through walls. These pin the player-keyed branch (DrawInside root stays the viewer cell).
|
||||
|
||||
[Fact]
|
||||
public void ShouldRenderIndoor_PlayerOutside_CameraInside_ReturnsFalse()
|
||||
{
|
||||
// THE doorway-grey regression: the player stepped onto a landcell (0x...0031) but the
|
||||
// chase camera still resolves an interior EnvCell. Branch on the PLAYER → outdoor.
|
||||
Assert.False(RenderingDiagnostics.ShouldRenderIndoor(playerCellId: 0xA9B40031u, viewerCellResolved: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRenderIndoor_PlayerInside_CameraInside_ReturnsTrue()
|
||||
{
|
||||
Assert.True(RenderingDiagnostics.ShouldRenderIndoor(playerCellId: 0xA9B40171u, viewerCellResolved: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRenderIndoor_PlayerInside_NoViewerCell_ReturnsFalse()
|
||||
{
|
||||
// Opposite lag (camera pulled outside while the player is inside): no viewer cell to
|
||||
// root DrawInside at → outdoor. Defensive; matches prior null-CameraCell behavior.
|
||||
Assert.False(RenderingDiagnostics.ShouldRenderIndoor(playerCellId: 0xA9B40171u, viewerCellResolved: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRenderIndoor_PlayerOutside_CameraOutside_ReturnsFalse()
|
||||
{
|
||||
Assert.False(RenderingDiagnostics.ShouldRenderIndoor(playerCellId: 0xA9B40031u, viewerCellResolved: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRenderIndoor_UnknownPlayerCell_TreatedAsOutside_ReturnsFalse()
|
||||
{
|
||||
// playerCellId == 0 (unresolved) → treat as outside (safe default: outdoor render).
|
||||
Assert.False(RenderingDiagnostics.ShouldRenderIndoor(playerCellId: 0u, viewerCellResolved: true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue