From e7058caa793a6e4c66cade5e517f2efca0341b5b Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 13 Jun 2026 18:05:36 +0200 Subject: [PATCH] Revert "fix(G.3a): hydrate EnvCell physics + visibility independent of render mesh (#133)" This reverts commit ab050a015fdd0cded7b54d7483dc451c22156c36. --- src/AcDream.App/Rendering/GameWindow.cs | 75 +++++++++++-------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 290b70e9..1c1db412 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5630,51 +5630,25 @@ public sealed class GameWindow : IDisposable // as WorldEntity records below — they have real GfxObj MeshRefs that work // fine; EnvCellRenderer.RegisterCell receives an empty staticObjects list. var cellSubMeshes = AcDream.Core.Meshing.CellMesh.Build(envCell, cellStruct, _dats); - - // The cell transforms and the physics + visibility hydration are - // INDEPENDENT of whether the cell has drawable geometry. Retail - // couples neither collision nor portal visibility to a render mesh. - // Keep the small render lift out of physics; retail BSP contact - // planes use the EnvCell origin verbatim. The lift constant is - // shared with every draw-space consumer of portal polygons - // (OutsideView gate, seal/punch fans) — see - // PortalVisibilityBuilder.ShellDrawLiftZ (#130). - var physicsCellOrigin = envCell.Position.Origin + lbOffset; - var cellOrigin = physicsCellOrigin + new System.Numerics.Vector3( - 0f, 0f, AcDream.App.Rendering.PortalVisibilityBuilder.ShellDrawLiftZ); - var cellTransform = - System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) * - System.Numerics.Matrix4x4.CreateTranslation(cellOrigin); - var physicsCellTransform = - System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) * - System.Numerics.Matrix4x4.CreateTranslation(physicsCellOrigin); - - // G.3a (#133) hydration decouple: BuildLoadedCell + CacheCellStruct - // were previously gated behind `cellSubMeshes.Count > 0`, which - // silently dropped collision (CellTransit.GetCellStruct -> null -> - // fall through floor) and the visibility node for any geometry-less - // collision cell. CacheCellStruct self-gates on a null PhysicsBSP - // (PhysicsDataCache), so this is safe for cells with no physics. - // Note CacheCellStruct DOES register every resolved cell into the UCG - // CellGraph unconditionally (before that BSP gate), so a geometry-less - // cell now enters the graph — but it never enters the _cellStruct BSP - // dictionary that membership/placement resolve through, so the player - // can never be rooted in such a cell. The added graph entry is inert. - // - // BuildLoadedCell uses the PHYSICS (unlifted) transform. The +0.02 m - // render lift above is a DRAW concern (shell z-fighting vs terrain); - // feeding it into the visibility graph shifted every HORIZONTAL portal - // plane 2 cm up, side-culling decks/landings (#119-residual, - // TowerAscentReplayTests.CapturedTopOfStairs_*). Vertical doorways were - // immune (the lift slides their planes along themselves). - BuildLoadedCell(envCellId, envCell, cellStruct, physicsCellOrigin, physicsCellTransform); - _physicsDataCache.CacheCellStruct(envCellId, envCell, cellStruct, physicsCellTransform); - - // Render registration only when the cell actually has drawable submeshes. if (cellSubMeshes.Count > 0) { _pendingCellMeshes[envCellId] = cellSubMeshes; + // Keep the small render lift out of physics; retail BSP + // contact planes use the EnvCell origin verbatim. The lift + // constant is shared with every draw-space consumer of + // portal polygons (OutsideView gate, seal/punch fans) — + // see PortalVisibilityBuilder.ShellDrawLiftZ (#130). + var physicsCellOrigin = envCell.Position.Origin + lbOffset; + var cellOrigin = physicsCellOrigin + new System.Numerics.Vector3( + 0f, 0f, AcDream.App.Rendering.PortalVisibilityBuilder.ShellDrawLiftZ); + var cellTransform = + System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) * + System.Numerics.Matrix4x4.CreateTranslation(cellOrigin); + var physicsCellTransform = + System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) * + System.Numerics.Matrix4x4.CreateTranslation(physicsCellOrigin); + // Phase A8: register the cell with EnvCellRenderer for rendering. // staticObjects is empty — cell stabs continue as separate WorldEntity // records via the dispatcher (see lines below for the unchanged stab path). @@ -5687,6 +5661,25 @@ public sealed class GameWindow : IDisposable cellWorldPosition: cellOrigin, cellRotation: envCell.Position.Orientation, staticObjects: System.Array.Empty<(uint, System.Numerics.Vector3, System.Numerics.Quaternion, bool, System.Numerics.Matrix4x4)>()); + + // Step 4: build LoadedCell for portal visibility — with the + // PHYSICS (unlifted) transform. The +0.02 m render lift above + // is a DRAW concern (shell z-fighting vs terrain); feeding it + // into the visibility graph shifted every HORIZONTAL portal + // plane 2 cm up, putting an eye standing on a deck/landing + // 10–20 mm BELOW the lifted plane — outside the side test's + // ±10 mm in-plane window — so the cell behind the portal was + // side-culled: the tower-top staircase vanish + roof flap + // (#119-residual; captured live at eye z=126.803 vs the + // 010A→0107 plane at 126.80, reproduced ONLY with the lift in + // TowerAscentReplayTests.CapturedTopOfStairs_*). Vertical + // doorways were immune (the lift slides their planes along + // themselves), which is why this hit exactly stairs, decks, + // and cellar mouths. + BuildLoadedCell(envCellId, envCell, cellStruct, physicsCellOrigin, physicsCellTransform); + + // Cache CellStruct physics BSP for indoor collision (UNCHANGED). + _physicsDataCache.CacheCellStruct(envCellId, envCell, cellStruct, physicsCellTransform); } } }