From de8ffde4ca80a3247fc8e8ddb6198fd0e592cdc5 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 20 May 2026 08:00:16 +0200 Subject: [PATCH] fix(physics): pass cell world-transform to indoor BSP collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Indoor cell BSP queries at TransitionTypes.cs:1442 were calling BSPQuery.FindCollisions with Quaternion.Identity + defaulted Vector3.Zero worldOrigin. Inside the BSP, Path 3 (step_sphere_down) and Path 4 (land-on-surface) use those params to build the world-space ContactPlane. Result: planes written with D ~ 0 instead of the cell's world floor Z (e.g. -94.02 for Holtburg cottages). 320 corrupt CP writes per Holtburg session per the [cp-write] probe. Fix: decompose cellPhysics.WorldTransform once at the call site, pass the rotation as localToWorld and the translation as worldOrigin. Mirrors the existing correct pattern at :1808 (FindObjCollisions, passes obj.Rotation + obj.Position). Retail oracle: BSPTREE::find_collisions (acclient_2013_pseudo_c.txt:323924) calls Plane::localtoglobal at :323921 before set_contact_plane. Our TransformNormal + TransformVertices + BuildWorldPlane chain is the equivalent — it just needs the right rotation + origin. Spec: docs/superpowers/specs/2026-05-20-indoor-bsp-worldorigin-fix-design.md. Plan: docs/superpowers/plans/2026-05-20-indoor-bsp-worldorigin-fix.md. Evidence: launch-cp-probe.log capture 2026-05-20, [cp-write] probe. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/TransitionTypes.cs | 37 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 18bab97..c941f67 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -1439,6 +1439,38 @@ public sealed class Transition // Use the full 6-path BSP dispatcher for retail-faithful collision. // Use pre-resolved polygons (vertices+planes computed at cache time). + // + // 2026-05-20 (Bug B fix): pass the cell's world rotation + + // translation so the BSP's internal find_walkable / + // step_sphere_down / Path-4 land write world-space ContactPlanes + // (NOT cell-local). The prior call passed Quaternion.Identity + // and defaulted worldOrigin = Vector3.Zero — which corrupted + // every Path 3 + Path 4 CP write inside an indoor cell with + // D ≈ 0 instead of D = -world_floor_Z. Mirrors the existing + // correct pattern at the FindObjCollisions call site (~line + // 1808: passes obj.Rotation + worldOrigin: obj.Position). + // + // Retail oracle: BSPTREE::find_collisions (decomp + // acclient_2013_pseudo_c.txt:323924) calls Plane::localtoglobal + // (:323921) before set_contact_plane. Our TransformNormal + + // TransformVertices + BuildWorldPlane chain is the equivalent + // — it just needs the right rotation + origin. + Quaternion cellRotation; + Vector3 cellOrigin; + if (!Matrix4x4.Decompose(cellPhysics.WorldTransform, out _, out cellRotation, out cellOrigin)) + { + // Matrix has shear or other non-decomposable parts. EnvCell + // WorldTransform is always rigid rotation + translation per + // the dat format, so this branch should never fire. Log a + // warning + fall back to identity rotation + .Translation + // so we degrade to "translation-only" instead of the prior + // "both broken". + Console.WriteLine(System.FormattableString.Invariant( + $"[indoor-bsp] WARN cellPhysics.WorldTransform did not decompose cleanly for cell 0x{sp.CheckCellId:X8} — falling back to identity rotation")); + cellRotation = Quaternion.Identity; + cellOrigin = cellPhysics.WorldTransform.Translation; + } + var cellState = BSPQuery.FindCollisions( cellPhysics.BSP.Root, cellPhysics.Resolved, @@ -1448,8 +1480,9 @@ public sealed class Transition localCurrCenter, Vector3.UnitZ, // local space Z is up 1.0f, // scale = 1.0 for cell geometry - Quaternion.Identity, - engine); // engine needed for Path 5 step-up + cellRotation, + engine, // engine needed for Path 5 step-up + worldOrigin: cellOrigin); if (PhysicsDiagnostics.ProbeIndoorBspEnabled) {