feat(render): V1 — render keys on the viewer cell+eye; lighting stays on the player
Phase W single-viewpoint V1 (un-split). The render mode decision, indoor root, and portal side-test now key on the collided-camera viewer cell + eye (RetailChaseCamera.ViewerCellId + camPos) — retail RenderNormalMode -> DrawInside(viewer_cell) @92675; InitCell side-test vs viewer.viewpoint @432991. Lighting / seen_outside / playerInsideCell stay on the PLAYER cell (CurrCell), retail CellManager::ChangePosition @4559B0. The old per-render player-root + eye-projection split (U.4c) is removed; the flap is avoided by the robust graph-tracked viewer cell (no AABB, no grace). [flap-cam] probe extended with viewerCell vs playerCell. CurrCell stays player-only (blue-hole fix intact). App 176 green; Core 1295/5 baseline (no new fails). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d03fe84845
commit
1e9a9cab8c
1 changed files with 50 additions and 40 deletions
|
|
@ -7148,42 +7148,48 @@ public sealed class GameWindow : IDisposable
|
||||||
// Step 4: portal visibility — compute BEFORE the UBO upload so
|
// Step 4: portal visibility — compute BEFORE the UBO upload so
|
||||||
// the indoor flag drives the sun's intensity to zero for
|
// the indoor flag drives the sun's intensity to zero for
|
||||||
// dungeons (r13 §13.7).
|
// dungeons (r13 §13.7).
|
||||||
// Phase U.4c (2026-05-31): root indoor visibility at the PLAYER's cell, not the
|
// Phase W single-viewpoint V1 (2026-06-03): the render keys on ONE viewpoint — the
|
||||||
// camera EYE. Retail's CellManager::ChangePosition (0x004559B0) tracks curr_cell by
|
// collided camera ("viewer") — exactly like retail (RenderNormalMode @ 0x453aa0 →
|
||||||
// the player/physics position. The 3rd-person chase EYE drifts out of the player's
|
// DrawInside(viewer_cell) pc:92675; InitCell side-test vs viewer.viewpoint pc:432991).
|
||||||
// cell (through interior walls into AABB gaps); FindCameraCell then can't place the
|
// The viewer cell is the camera-collision sweep's swept cell
|
||||||
// eye and returns the STALE previous cell for its 3 grace frames, from which the
|
// (RetailChaseCamera.ViewerCellId = retail viewer_cell = sphere_path.curr_cell):
|
||||||
// doorway portal is "behind" the eye → culled → the exit cell + terrain + shells
|
// graph-tracked, deterministic, NO AABB / NO grace frames — so the U.4c flap source
|
||||||
// flap off. ACDREAM_PROBE_FLAP capture (2026-05-31): every flap frame is
|
// (stale FindCameraCell over grace frames) is gone WITHOUT splitting viewpoints.
|
||||||
// res=Grace eyeInRoot=n terrain=Skip; every good frame is eyeInRoot=Y. The eye is
|
// SEPARATELY, lighting / seen_outside key on the PLAYER cell (CurrCell), matching retail
|
||||||
// still used for the per-frame PROJECTION (envCellViewProj) — only the cell ROOT +
|
// CellManager::ChangePosition @ 0x4559B0 — the player's cell, not the camera's, decides
|
||||||
// portal-side test track the player. This mirrors the playerInsideCell lighting
|
// whether the sun dies (sealed interior). retail player->cell (physics/lighting) vs
|
||||||
// decision below, which already roots at the player for exactly this reason.
|
// SmartBox->viewer_cell (render); the old per-render player-root + eye-projection split is gone.
|
||||||
var visRootPos = (_playerMode && _playerController is not null)
|
|
||||||
? _playerController.Position
|
// ── Lighting root: the PLAYER cell (CurrCell). ──
|
||||||
: camPos;
|
LoadedCell? playerRoot = null;
|
||||||
// UCG W2: use the physics membership answer (DataCache.CellGraph.CurrCell) as the
|
if (_physicsEngine.DataCache?.CellGraph.CurrCell is AcDream.Core.World.Cells.EnvCell playerCellObj
|
||||||
// BFS root instead of resolving from position via FindCameraCell. Falls back to the
|
&& _cellVisibility.TryGetCell(playerCellObj.Id, out var playerRegCell))
|
||||||
// original ComputeVisibility path when the physics answer isn't usable yet (null
|
playerRoot = playerRegCell;
|
||||||
// CurrCell, or its cell id not yet registered with the render CellVisibility system).
|
bool playerSeenOutside = playerRoot?.SeenOutside ?? true;
|
||||||
// This closes the render/physics disagreement — both now key off the same BSP-based
|
|
||||||
// resolution — which is the root cause of the "world from below" spawn flicker.
|
// ── Render root: the VIEWER (collided camera) cell + eye. ──
|
||||||
LoadedCell? physicsRoot = null;
|
// Default (player mode + retail chase cam): the sweep's viewer cell. Fallback for the
|
||||||
if (_physicsEngine.DataCache?.CellGraph.CurrCell is AcDream.Core.World.Cells.EnvCell physCell
|
// non-default legacy/debug camera paths: the player's registered cell (or none).
|
||||||
&& _cellVisibility.TryGetCell(physCell.Id, out var registeredCell))
|
uint viewerCellId =
|
||||||
physicsRoot = registeredCell;
|
(_playerMode && _retailChaseCamera is not null
|
||||||
var visibility = _cellVisibility.ComputeVisibilityFromRoot(physicsRoot, visRootPos);
|
&& AcDream.Core.Rendering.CameraDiagnostics.UseRetailChaseCamera)
|
||||||
|
? _retailChaseCamera.ViewerCellId
|
||||||
|
: (playerRoot?.CellId ?? 0u);
|
||||||
|
var viewerEyePos = camPos; // the collided eye drives the side-test AND the projection
|
||||||
|
LoadedCell? viewerRoot = null;
|
||||||
|
if ((viewerCellId & 0xFFFFu) >= 0x0100u
|
||||||
|
&& _cellVisibility.TryGetCell(viewerCellId, out var viewerRegCell))
|
||||||
|
viewerRoot = viewerRegCell;
|
||||||
|
var visibility = _cellVisibility.ComputeVisibilityFromRoot(viewerRoot, viewerEyePos);
|
||||||
bool cameraInsideCell = visibility?.CameraCell is not null;
|
bool cameraInsideCell = visibility?.CameraCell is not null;
|
||||||
|
|
||||||
// Stage 3 (2026-06-02): extract seen_outside from the PVS root cell.
|
// Stage 3 (2026-06-02): the RENDER's seen_outside (gates terrain/sky through the
|
||||||
// Retail CellManager::ChangePosition @ 0x004559B0 (pseudo_c:94649):
|
// doorway) comes from the VIEWER root cell. Retail CellManager::ChangePosition
|
||||||
// "if (seen_outside || keep_lscape_loaded) keep landscape + terrain
|
// @ 0x004559B0 (pseudo_c:94649): keep landscape+terrain iff seen_outside else release.
|
||||||
// else LScape::release_all (dungeon)"
|
// Outdoor viewer (viewerRoot==null) → always seen_outside=true.
|
||||||
// Outdoor root (physicsRoot==null) → always seen_outside=true.
|
// Building interior with exit portal → seen_outside=true (terrain clipped to the door).
|
||||||
// Building interior with exit portal → seen_outside=true (sky/terrain kept live;
|
|
||||||
// clipped to doorway in Stage 4).
|
|
||||||
// Pure dungeon (no exit portal reachable) → seen_outside=false (sky suppressed).
|
// Pure dungeon (no exit portal reachable) → seen_outside=false (sky suppressed).
|
||||||
bool rootSeenOutside = physicsRoot?.SeenOutside ?? true;
|
bool rootSeenOutside = viewerRoot?.SeenOutside ?? true;
|
||||||
|
|
||||||
// Phase U.4 (2026-05-30): the [vis] probe moved DOWN to the unified
|
// Phase U.4 (2026-05-30): the [vis] probe moved DOWN to the unified
|
||||||
// gated-draw block (after envCellViewProj exists) where it can report
|
// gated-draw block (after envCellViewProj exists) where it can report
|
||||||
|
|
@ -7198,8 +7204,9 @@ public sealed class GameWindow : IDisposable
|
||||||
// independent AABB containment scan. playerInsideCell = true (kill sunlight) only
|
// independent AABB containment scan. playerInsideCell = true (kill sunlight) only
|
||||||
// when the player is inside a SEALED interior (seen_outside=false = dungeon).
|
// when the player is inside a SEALED interior (seen_outside=false = dungeon).
|
||||||
// Building interiors with seen_outside=true keep the sun (sky visible through door).
|
// Building interiors with seen_outside=true keep the sun (sky visible through door).
|
||||||
// When not in player mode (orbit/fly debug camera) we fall back to cameraInsideCell.
|
// V1 (2026-06-03): keyed on the PLAYER cell (playerRoot/playerSeenOutside), independent
|
||||||
bool playerInsideCell = cameraInsideCell && !rootSeenOutside;
|
// of the camera's viewer cell — retail kills the sun off the player's cell, not the eye.
|
||||||
|
bool playerInsideCell = playerRoot is not null && !playerSeenOutside;
|
||||||
|
|
||||||
// Phase C.1: tick retail PhysicsScript particle hooks. Named
|
// Phase C.1: tick retail PhysicsScript particle hooks. Named
|
||||||
// retail decomp confirms SkyObject.PesObjectId is copied by
|
// retail decomp confirms SkyObject.PesObjectId is copied by
|
||||||
|
|
@ -7320,12 +7327,13 @@ public sealed class GameWindow : IDisposable
|
||||||
HashSet<uint>? envCellShellFilter = null; // drawable visible cells (cellIdToSlot keys)
|
HashSet<uint>? envCellShellFilter = null; // drawable visible cells (cellIdToSlot keys)
|
||||||
if (clipRoot is not null)
|
if (clipRoot is not null)
|
||||||
{
|
{
|
||||||
// Phase U.4c: side test + distance ordering use the PLAYER position (visRootPos,
|
// Phase W single-viewpoint V1 (2026-06-03): the portal side test + distance ordering
|
||||||
// stable inside the cell); projection uses the eye's envCellViewProj (the screen
|
// use the VIEWER eye (the collided camera) — same viewpoint as the projection
|
||||||
// view). See the visRootPos rationale at the ComputeVisibility call above.
|
// (envCellViewProj) and the render root (clipRoot = the viewer cell). ONE viewpoint,
|
||||||
|
// retail InitCell side-test vs viewer.viewpoint (pc:432991). No more player/eye split.
|
||||||
pvFrame = PortalVisibilityBuilder.Build(
|
pvFrame = PortalVisibilityBuilder.Build(
|
||||||
clipRoot,
|
clipRoot,
|
||||||
visRootPos,
|
viewerEyePos,
|
||||||
id => _cellVisibility.TryGetCell(id, out var c) ? c : null,
|
id => _cellVisibility.TryGetCell(id, out var c) ? c : null,
|
||||||
envCellViewProj);
|
envCellViewProj);
|
||||||
|
|
||||||
|
|
@ -7369,8 +7377,10 @@ public sealed class GameWindow : IDisposable
|
||||||
{
|
{
|
||||||
var flapPlayer = _playerController?.Position ?? camPos;
|
var flapPlayer = _playerController?.Position ?? camPos;
|
||||||
bool eyeInRoot = CellVisibility.PointInCell(camPos, clipRoot);
|
bool eyeInRoot = CellVisibility.PointInCell(camPos, clipRoot);
|
||||||
|
uint flapPlayerCell = _physicsEngine.DataCache?.CellGraph.CurrCell?.Id ?? 0u;
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"[flap-cam] root=0x{clipRoot.CellId:X8} res={_cellVisibility.LastCameraCellResolution} " +
|
$"[flap-cam] root=0x{clipRoot.CellId:X8} viewerCell=0x{viewerCellId:X8} playerCell=0x{flapPlayerCell:X8} " +
|
||||||
|
$"res={_cellVisibility.LastCameraCellResolution} " +
|
||||||
$"eyeInRoot={(eyeInRoot ? "Y" : "n")} eye=({camPos.X:F2},{camPos.Y:F2},{camPos.Z:F2}) " +
|
$"eyeInRoot={(eyeInRoot ? "Y" : "n")} eye=({camPos.X:F2},{camPos.Y:F2},{camPos.Z:F2}) " +
|
||||||
$"player=({flapPlayer.X:F2},{flapPlayer.Y:F2},{flapPlayer.Z:F2}) " +
|
$"player=({flapPlayer.X:F2},{flapPlayer.Y:F2},{flapPlayer.Z:F2}) " +
|
||||||
$"terrain={clipAssembly.TerrainMode} outVisible={clipAssembly.OutdoorVisible}");
|
$"terrain={clipAssembly.TerrainMode} outVisible={clipAssembly.OutdoorVisible}");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue