feat(render): Stage 3 T3.2 — seen_outside terrain/sky gate per CellManager::ChangePosition
Port retail CellManager::ChangePosition @ 0x004559B0 (pseudo_c:94649) landscape policy. Three changes in GameWindow.OnRender: 1. Extract rootSeenOutside = physicsRoot?.SeenOutside ?? true after ComputeVisibilityFromRoot (outdoor null root → always seen_outside=true). 2. Replace IsInsideAnyCell AABB scan with seen_outside-derived predicate: playerInsideCell = cameraInsideCell && !rootSeenOutside. Semantics: sun zeroed only in sealed interior (dungeon); building interiors with seen_outside keep the sun (sky visible through door). 3. renderSky = !cameraInsideCell || rootSeenOutside (Stage 3 gate, interim: sky draws full-screen in building interiors until Stage 4 clips to doorway). 4. Weather gate updated to follow renderSky (seen_outside policy). Retail anchors: CellManager::ChangePosition 0x004559B0 (landscape/sun policy), SmartBox::RenderNormalMode 0x00453aa0 (sky gate per seen_outside). NOTE: Interim regression — sky renders full-screen indoors for seen_outside cells until Stage 4 wires OutsideView clip. Expected per EXECUTION POLICY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a1fbbd44e
commit
352086042e
1 changed files with 30 additions and 15 deletions
|
|
@ -7166,6 +7166,16 @@ public sealed class GameWindow : IDisposable
|
|||
var visibility = _cellVisibility.ComputeVisibilityFromRoot(physicsRoot, visRootPos);
|
||||
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).
|
||||
// Pure dungeon (no exit portal reachable) → seen_outside=false (sky suppressed).
|
||||
bool rootSeenOutside = physicsRoot?.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
|
||||
// the real PortalVisibilityFrame — OutsideView polygon/plane counts and
|
||||
|
|
@ -7173,18 +7183,14 @@ public sealed class GameWindow : IDisposable
|
|||
// of the old camera-state-only spike. See the U.4 ClipFrame assembly
|
||||
// below (gated on ACDREAM_PROBE_VIS=1, cell-change-throttled).
|
||||
|
||||
// Lighting decisions (sun zeroed, indoor ambient applied) must
|
||||
// track the PLAYER's cell, not the camera's. In third-person
|
||||
// chase mode the camera enters interiors before the player body
|
||||
// does, so a camera-based trigger flips the scene to indoor
|
||||
// lighting prematurely. Retail's CellManager::ChangePosition
|
||||
// @ 0x004559B0 reads CObjCell::seen_outside on the player's
|
||||
// current cell — that's the semantics we want here. When the
|
||||
// player isn't in player mode (orbit / fly debug camera) we
|
||||
// fall back to the camera trigger.
|
||||
bool playerInsideCell = (_playerMode && _playerController is not null)
|
||||
? _cellVisibility.IsInsideAnyCell(_playerController.Position)
|
||||
: cameraInsideCell;
|
||||
// Stage 3 (2026-06-02): replace the IsInsideAnyCell AABB scan with the
|
||||
// seen_outside-derived predicate. Retail CellManager::ChangePosition (0x004559B0)
|
||||
// gates sun/lighting off seen_outside on the player's current cell, NOT off an
|
||||
// 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;
|
||||
|
||||
// Phase C.1: tick retail PhysicsScript particle hooks. Named
|
||||
// retail decomp confirms SkyObject.PesObjectId is copied by
|
||||
|
|
@ -7264,7 +7270,14 @@ public sealed class GameWindow : IDisposable
|
|||
// cylinder 0x01004C42/0x01004C44) need to overlay terrain and
|
||||
// entities to look volumetric — see the post-scene RenderWeather
|
||||
// call further below.
|
||||
bool renderSky = !cameraInsideCell;
|
||||
// Stage 3 (2026-06-02): sky gate uses seen_outside per retail RenderNormalMode:92649.
|
||||
// Outdoor root (cameraInsideCell=false): always render sky.
|
||||
// Building interior (cameraInsideCell=true, rootSeenOutside=true): render sky —
|
||||
// it draws full-screen here until Stage 4 clips it to the doorway via OutsideView.
|
||||
// Sealed dungeon (cameraInsideCell=true, rootSeenOutside=false): no sky.
|
||||
// NOTE: interim regression until Stage 4 — sky draws full-screen in building interiors.
|
||||
// This is expected per the EXECUTION POLICY; do NOT add a workaround gate.
|
||||
bool renderSky = !cameraInsideCell || rootSeenOutside;
|
||||
if (renderSky)
|
||||
{
|
||||
_skyRenderer?.RenderSky(camera, camPos, (float)WorldTime.DayFraction,
|
||||
|
|
@ -7502,8 +7515,10 @@ public sealed class GameWindow : IDisposable
|
|||
// instead of being painted over by them. This is the second
|
||||
// half of retail's LScape::draw split — GameSky::Draw(1)
|
||||
// fires after the DrawBlock loop. Same indoor gate as the
|
||||
// sky pass: weather is suppressed inside cells.
|
||||
if (!cameraInsideCell)
|
||||
// sky pass: weather follows renderSky (seen_outside policy,
|
||||
// Stage 3: suppressed in sealed dungeons, visible in building
|
||||
// interiors through exit portals, always visible outdoors).
|
||||
if (renderSky)
|
||||
{
|
||||
_skyRenderer?.RenderWeather(camera, camPos, (float)WorldTime.DayFraction,
|
||||
_activeDayGroup, kf, environOverrideActive);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue