From 832001d289136506768616df73a64ab49cd046a4 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 3 Jun 2026 12:32:45 +0200 Subject: [PATCH] =?UTF-8?q?refactor(render):=20SweepEye=20returns=20(Eye,?= =?UTF-8?q?=20ViewerCellId)=20=E2=80=94=20surface=20the=20swept=20viewer?= =?UTF-8?q?=20cell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The camera spring-arm sweep already resolves the collided eye's cell (ResolveResult.CellId = sp.CurCellId = retail viewer_cell = sphere_path.curr_cell, update_viewer pc:92871). Return it from SweepEye so the render can root on the viewer cell (Phase W single-viewpoint V1, Task 1). Pure plumbing — behavior unchanged; callers extract .Eye. 174 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Rendering/ICameraCollisionProbe.cs | 15 ++++++++++++--- .../Rendering/PhysicsCameraCollisionProbe.cs | 11 +++++++---- src/AcDream.App/Rendering/RetailChaseCamera.cs | 2 +- .../Rendering/CameraCollisionIndoorTests.cs | 2 +- .../Rendering/PhysicsCameraCollisionProbeTests.cs | 3 ++- .../Rendering/RetailChaseCameraTests.cs | 9 +++++---- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/AcDream.App/Rendering/ICameraCollisionProbe.cs b/src/AcDream.App/Rendering/ICameraCollisionProbe.cs index 52fad70..0f05c9a 100644 --- a/src/AcDream.App/Rendering/ICameraCollisionProbe.cs +++ b/src/AcDream.App/Rendering/ICameraCollisionProbe.cs @@ -2,9 +2,17 @@ using System.Numerics; namespace AcDream.App.Rendering; +/// +/// Result of a camera spring-arm sweep: the collided eye position AND the cell the swept +/// viewer-sphere ended in (retail viewer_cell = sphere_path.curr_cell, update_viewer +/// pc:92871). The cell is graph-tracked by the transition — no AABB, no grace frames — so it +/// is the robust per-frame "which cell is the camera in?" answer that roots the render. +/// +public readonly record struct CameraSweepResult(Vector3 Eye, uint ViewerCellId); + /// /// Sweeps a small sphere from the camera pivot (player head) toward the -/// desired eye and returns the stopped (non-penetrating) eye. The seam that +/// desired eye and returns the stopped (non-penetrating) eye + its cell. The seam that /// lets collide its eye without depending on /// the physics engine directly (and stay unit-testable with a fake). /// @@ -13,8 +21,9 @@ public interface ICameraCollisionProbe /// /// Roll a collision sphere from to /// ; return the position it reaches without - /// penetrating geometry. Returns unchanged + /// penetrating geometry AND the cell it ended in. Returns + /// + unchanged /// when nothing blocks the path or when is 0. /// - Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId); + CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId); } diff --git a/src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs b/src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs index 8f94e6e..4dc98c8 100644 --- a/src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs +++ b/src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs @@ -21,10 +21,10 @@ public sealed class PhysicsCameraCollisionProbe : ICameraCollisionProbe public PhysicsCameraCollisionProbe(PhysicsEngine physics) => _physics = physics; - public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) + public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) { - // No starting cell → nothing to sweep against; keep the desired eye. - if (cellId == 0) return desiredEye; + // No starting cell → nothing to sweep against; keep the desired eye + cell. + if (cellId == 0) return new CameraSweepResult(desiredEye, cellId); // SpherePath.InitPath puts sphere0's center at pathPos + (0,0,radius) // (the player foot-capsule convention). Retail's viewer_sphere center is @@ -80,7 +80,10 @@ public sealed class PhysicsCameraCollisionProbe : ICameraCollisionProbe $"collNormValid={r.CollisionNormalValid}"); } - return eye; + // Phase W single-viewpoint V1 (2026-06-03): surface the swept cell (r.CellId = + // sp.CurCellId) as the viewer cell — retail viewer_cell = sphere_path.curr_cell + // (update_viewer pc:92871). Graph-tracked, no AABB/grace → the U.4c flap source is gone. + return new CameraSweepResult(eye, r.CellId); } /// Eye/pivot point → InitPath path point (subtract the sphere-center offset). diff --git a/src/AcDream.App/Rendering/RetailChaseCamera.cs b/src/AcDream.App/Rendering/RetailChaseCamera.cs index 79de8f4..993999a 100644 --- a/src/AcDream.App/Rendering/RetailChaseCamera.cs +++ b/src/AcDream.App/Rendering/RetailChaseCamera.cs @@ -153,7 +153,7 @@ public sealed class RetailChaseCamera : ICamera // leave _dampedEye as the clean, uncollided sought position. Vector3 publishedEye = _dampedEye; if (CameraDiagnostics.CollideCamera && CollisionProbe is not null) - publishedEye = CollisionProbe.SweepEye(pivotWorld, _dampedEye, cellId, selfEntityId); + publishedEye = CollisionProbe.SweepEye(pivotWorld, _dampedEye, cellId, selfEntityId).Eye; // 6. Publish renderer surface (from the collided eye; rotation stays the // smoothly-damped look direction toward the pivot). diff --git a/tests/AcDream.App.Tests/Rendering/CameraCollisionIndoorTests.cs b/tests/AcDream.App.Tests/Rendering/CameraCollisionIndoorTests.cs index 0d859c6..d0fb3b6 100644 --- a/tests/AcDream.App.Tests/Rendering/CameraCollisionIndoorTests.cs +++ b/tests/AcDream.App.Tests/Rendering/CameraCollisionIndoorTests.cs @@ -140,7 +140,7 @@ public class CameraCollisionIndoorTests pivot: PivotWorld, desiredEye: DesiredEye, cellId: IndoorCellId, - selfEntityId: 0u); + selfEntityId: 0u).Eye; // The eye should be stopped before the exterior wall at Y=4.0. // Expected stopped eye Y ≈ 4.0 - ViewerSphereRadius = 3.7. diff --git a/tests/AcDream.App.Tests/Rendering/PhysicsCameraCollisionProbeTests.cs b/tests/AcDream.App.Tests/Rendering/PhysicsCameraCollisionProbeTests.cs index 5cb7b0c..c756c94 100644 --- a/tests/AcDream.App.Tests/Rendering/PhysicsCameraCollisionProbeTests.cs +++ b/tests/AcDream.App.Tests/Rendering/PhysicsCameraCollisionProbeTests.cs @@ -38,6 +38,7 @@ public class PhysicsCameraCollisionProbeTests var result = probe.SweepEye(pivot, eye, cellId: 0, selfEntityId: 0); - Assert.Equal(eye, result); + Assert.Equal(eye, result.Eye); + Assert.Equal(0u, result.ViewerCellId); // cellId==0 → returned unchanged } } diff --git a/tests/AcDream.App.Tests/Rendering/RetailChaseCameraTests.cs b/tests/AcDream.App.Tests/Rendering/RetailChaseCameraTests.cs index 8a3063b..0e58afb 100644 --- a/tests/AcDream.App.Tests/Rendering/RetailChaseCameraTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RetailChaseCameraTests.cs @@ -452,10 +452,11 @@ public class RetailChaseCameraTests { public int Calls; public Vector3 ReturnEye; - public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) + public uint ReturnCell; + public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) { Calls++; - return ReturnEye; + return new CameraSweepResult(ReturnEye, ReturnCell); } } @@ -538,10 +539,10 @@ public class RetailChaseCameraTests { public int Calls; public Vector3 ClampEye; - public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) + public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId) { Calls++; - return Calls == 1 ? ClampEye : desiredEye; + return new CameraSweepResult(Calls == 1 ? ClampEye : desiredEye, cellId); } }