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
|
||||
// the indoor flag drives the sun's intensity to zero for
|
||||
// dungeons (r13 §13.7).
|
||||
// Phase U.4c (2026-05-31): root indoor visibility at the PLAYER's cell, not the
|
||||
// camera EYE. Retail's CellManager::ChangePosition (0x004559B0) tracks curr_cell by
|
||||
// the player/physics position. The 3rd-person chase EYE drifts out of the player's
|
||||
// cell (through interior walls into AABB gaps); FindCameraCell then can't place the
|
||||
// eye and returns the STALE previous cell for its 3 grace frames, from which the
|
||||
// doorway portal is "behind" the eye → culled → the exit cell + terrain + shells
|
||||
// flap off. ACDREAM_PROBE_FLAP capture (2026-05-31): every flap frame is
|
||||
// res=Grace eyeInRoot=n terrain=Skip; every good frame is eyeInRoot=Y. The eye is
|
||||
// still used for the per-frame PROJECTION (envCellViewProj) — only the cell ROOT +
|
||||
// portal-side test track the player. This mirrors the playerInsideCell lighting
|
||||
// decision below, which already roots at the player for exactly this reason.
|
||||
var visRootPos = (_playerMode && _playerController is not null)
|
||||
? _playerController.Position
|
||||
: camPos;
|
||||
// UCG W2: use the physics membership answer (DataCache.CellGraph.CurrCell) as the
|
||||
// BFS root instead of resolving from position via FindCameraCell. Falls back to the
|
||||
// original ComputeVisibility path when the physics answer isn't usable yet (null
|
||||
// CurrCell, or its cell id not yet registered with the render CellVisibility system).
|
||||
// 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.
|
||||
LoadedCell? physicsRoot = null;
|
||||
if (_physicsEngine.DataCache?.CellGraph.CurrCell is AcDream.Core.World.Cells.EnvCell physCell
|
||||
&& _cellVisibility.TryGetCell(physCell.Id, out var registeredCell))
|
||||
physicsRoot = registeredCell;
|
||||
var visibility = _cellVisibility.ComputeVisibilityFromRoot(physicsRoot, visRootPos);
|
||||
// Phase W single-viewpoint V1 (2026-06-03): the render keys on ONE viewpoint — the
|
||||
// collided camera ("viewer") — exactly like retail (RenderNormalMode @ 0x453aa0 →
|
||||
// DrawInside(viewer_cell) pc:92675; InitCell side-test vs viewer.viewpoint pc:432991).
|
||||
// The viewer cell is the camera-collision sweep's swept cell
|
||||
// (RetailChaseCamera.ViewerCellId = retail viewer_cell = sphere_path.curr_cell):
|
||||
// graph-tracked, deterministic, NO AABB / NO grace frames — so the U.4c flap source
|
||||
// (stale FindCameraCell over grace frames) is gone WITHOUT splitting viewpoints.
|
||||
// SEPARATELY, lighting / seen_outside key on the PLAYER cell (CurrCell), matching retail
|
||||
// CellManager::ChangePosition @ 0x4559B0 — the player's cell, not the camera's, decides
|
||||
// whether the sun dies (sealed interior). retail player->cell (physics/lighting) vs
|
||||
// SmartBox->viewer_cell (render); the old per-render player-root + eye-projection split is gone.
|
||||
|
||||
// ── Lighting root: the PLAYER cell (CurrCell). ──
|
||||
LoadedCell? playerRoot = null;
|
||||
if (_physicsEngine.DataCache?.CellGraph.CurrCell is AcDream.Core.World.Cells.EnvCell playerCellObj
|
||||
&& _cellVisibility.TryGetCell(playerCellObj.Id, out var playerRegCell))
|
||||
playerRoot = playerRegCell;
|
||||
bool playerSeenOutside = playerRoot?.SeenOutside ?? true;
|
||||
|
||||
// ── Render root: the VIEWER (collided camera) cell + eye. ──
|
||||
// Default (player mode + retail chase cam): the sweep's viewer cell. Fallback for the
|
||||
// non-default legacy/debug camera paths: the player's registered cell (or none).
|
||||
uint viewerCellId =
|
||||
(_playerMode && _retailChaseCamera is not null
|
||||
&& 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;
|
||||
|
||||
// Stage 3 (2026-06-02): extract seen_outside from the PVS root cell.
|
||||
// Retail CellManager::ChangePosition @ 0x004559B0 (pseudo_c:94649):
|
||||
// "if (seen_outside || keep_lscape_loaded) keep landscape + terrain
|
||||
// else LScape::release_all (dungeon)"
|
||||
// Outdoor root (physicsRoot==null) → always seen_outside=true.
|
||||
// Building interior with exit portal → seen_outside=true (sky/terrain kept live;
|
||||
// clipped to doorway in Stage 4).
|
||||
// Stage 3 (2026-06-02): the RENDER's seen_outside (gates terrain/sky through the
|
||||
// doorway) comes from the VIEWER root cell. Retail CellManager::ChangePosition
|
||||
// @ 0x004559B0 (pseudo_c:94649): keep landscape+terrain iff seen_outside else release.
|
||||
// Outdoor viewer (viewerRoot==null) → always seen_outside=true.
|
||||
// Building interior with exit portal → seen_outside=true (terrain clipped to the door).
|
||||
// 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
|
||||
// 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
|
||||
// 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).
|
||||
// When not in player mode (orbit/fly debug camera) we fall back to cameraInsideCell.
|
||||
bool playerInsideCell = cameraInsideCell && !rootSeenOutside;
|
||||
// V1 (2026-06-03): keyed on the PLAYER cell (playerRoot/playerSeenOutside), independent
|
||||
// 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
|
||||
// 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)
|
||||
if (clipRoot is not null)
|
||||
{
|
||||
// Phase U.4c: side test + distance ordering use the PLAYER position (visRootPos,
|
||||
// stable inside the cell); projection uses the eye's envCellViewProj (the screen
|
||||
// view). See the visRootPos rationale at the ComputeVisibility call above.
|
||||
// Phase W single-viewpoint V1 (2026-06-03): the portal side test + distance ordering
|
||||
// use the VIEWER eye (the collided camera) — same viewpoint as the projection
|
||||
// (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(
|
||||
clipRoot,
|
||||
visRootPos,
|
||||
viewerEyePos,
|
||||
id => _cellVisibility.TryGetCell(id, out var c) ? c : null,
|
||||
envCellViewProj);
|
||||
|
||||
|
|
@ -7369,8 +7377,10 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
var flapPlayer = _playerController?.Position ?? camPos;
|
||||
bool eyeInRoot = CellVisibility.PointInCell(camPos, clipRoot);
|
||||
uint flapPlayerCell = _physicsEngine.DataCache?.CellGraph.CurrCell?.Id ?? 0u;
|
||||
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}) " +
|
||||
$"player=({flapPlayer.X:F2},{flapPlayer.Y:F2},{flapPlayer.Z:F2}) " +
|
||||
$"terrain={clipAssembly.TerrainMode} outVisible={clipAssembly.OutdoorVisible}");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue