From f3cb4b6cc937d6181c7ca22726495862d28cd1a8 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 17:45:18 +0200 Subject: [PATCH] fix(render) Campaign FW3 visual gate: sweep-fail frames root on the PLAYER cell Owner report: floor openings (stairs down) briefly covered by outside terrain at camera in/out crossings - the historic #119 family transition arm. Root cause: at a doorway graze the camera collision sweep can transiently fail; retail total-failure fallback set_viewer(&player->m_position, 1) (update_viewer @0x00453fad, and the cell==0 bail @0x00453d1d) copies the WHOLE player Position - objcell_id included - so retail roots those frames on the player interior cell. Our fallback left ViewerCellId = 0, which the walk reads as an OUTDOOR root while the eye is inside: full-screen terrain with the look-in gates correctly refusing from the interior side - grass over the stairwell for exactly the failing frames. The fallback now inherits the pivot/player cell, matching retail byte-for-byte. Suites: full Release build 0 warnings; hermetic 6,758/0. Co-Authored-By: Claude Fable 5 --- .../Rendering/RetailChaseCamera.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/AcDream.App/Rendering/RetailChaseCamera.cs b/src/AcDream.App/Rendering/RetailChaseCamera.cs index 5362b457..b227fc60 100644 --- a/src/AcDream.App/Rendering/RetailChaseCamera.cs +++ b/src/AcDream.App/Rendering/RetailChaseCamera.cs @@ -252,12 +252,23 @@ public sealed class RetailChaseCamera : ICamera var swept = CollisionProbe.SweepEye(pivotWorld, _soughtEye, cellId, selfEntityId, playerPosition); publishedEye = swept.Eye; ViewerCellId = swept.ViewerCellId; - // Total-failure fallback = retail set_viewer(player_pos, reset_sought=1) - // (update_viewer :92886 and the cell==0 bail :92775 — both surface here as - // ViewerCellId == 0): the sought resets to the returned position and - // re-extends from there. + // Total-failure fallback = retail set_viewer(&player->m_position, + // reset_sought=1) (update_viewer @0x00453fad and the cell==0 bail + // @0x00453d1d — both surface here as ViewerCellId == 0): the + // sought resets to the returned position and re-extends, AND the + // viewer INHERITS THE PLAYER'S POSITION CELL — set_viewer copies + // the whole Position, objcell_id included, so retail ROOTS ON THE + // PLAYER'S (interior) CELL for these frames. Leaving 0 here made + // the walk read the frame as OUTDOOR-rooted while the eye was + // inside a doorway graze: full-screen terrain with the look-in + // gates correctly refusing from the interior side — the FW3 + // visual gate's "grass briefly covers the stairwell at the + // crossing" report (the historic #119 family's transition arm). if (swept.ViewerCellId == 0) + { _soughtEye = swept.Eye; + ViewerCellId = cellId; + } } // Retail viewer — the base of next frame's interpolation (step 5). _publishedEye = publishedEye;