From b94a7e80171f889e25a78b4d20334494dcf3fcd0 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 11 Jun 2026 20:02:48 +0200 Subject: [PATCH] #126: outdoor restore grounds onto elevated walkables, not through them A zero-delta RESTORE of an outdoor claim standing far above terrain (logged out on a building roof deck - the AAB3 tower 0x010A slab at z=127.2 over terrain 112) was grounded to TERRAIN unconditionally, warping the player through the roof into the building interior, outdoor-classified -> the transparent-interior spawn the user hit on every login while the save sat on the roof. Retail restores settle via AdjustPosition onto real surfaces, not the heightmap. Fix: the snap outdoor branch, zero-delta shape only, when the claim z exceeds terrain by more than step height: ground to the nearest CELL WALKABLE at/below the claim z (the #111 WalkableFloorZNearest query - real floors only, never the ceiling soup), keeping the outdoor cell id (honest: a deck-stander center sits above the slab cell BSP - the same state the user played in all afternoon). GfxObj-shell roofs without cells not covered - file if a real case shows. Suites: App 242+1skip, Core 1422+2skip, UI 420, Net 294. Co-Authored-By: Claude Fable 5 --- src/AcDream.Core/Physics/PhysicsEngine.cs | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index a31740a9..ab20fee9 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -787,6 +787,45 @@ public sealed class PhysicsEngine // Stay outdoors on terrain. targetZ = terrainZ; targetCellId = physics.Terrain.ComputeOutdoorCellId(localCandX, localCandY); + + // #126 (2026-06-11): a zero-delta RESTORE of an OUTDOOR claim + // standing far ABOVE terrain means the player logged out on a + // walkable surface that is not the ground — a building roof + // deck (the AAB3 tower's 0x010A slab, z=127.2 over terrain + // 112). Grounding to terrain warps the player THROUGH the + // roof into the building's interior volume, outdoor-classified + // → the transparent-interior spawn. Retail's restore settles + // via AdjustPosition onto real surfaces, not the heightmap. + // Ground to the nearest CELL WALKABLE at/below the claim Z + // (the #111 walkable query — real floors only, never ceiling + // soup); the cell id stays OUTDOOR (the claim is honest: the + // player's center on a deck sits above the slab cell's BSP). + // GfxObj-shell roofs without cells are not covered — file if + // a real case shows. + if (snapDiag && currentPos.Z > terrainZ + stepUpHeight) + { + float? bestWalkZ = null; + float bestWalkDist = float.MaxValue; + foreach (var cell in physics.Cells) + { + float? wz = WalkableFloorZNearest( + lbPrefix | (cell.CellId & 0xFFFFu), candidatePos, currentPos.Z); + if (wz is null) continue; + if (wz.Value > currentPos.Z + stepUpHeight) continue; // above the claim — not the surface stood on + float dist = MathF.Abs(wz.Value - currentPos.Z); + if (dist < bestWalkDist) + { + bestWalkDist = dist; + bestWalkZ = wz; + } + } + if (bestWalkZ is not null && bestWalkZ.Value > terrainZ) + { + Console.WriteLine(System.FormattableString.Invariant( + $"[snap] OUTDOOR claim 0x{cellId:X8} at z={currentPos.Z:F3} stands on an elevated walkable z={bestWalkZ.Value:F3} (terrain {terrainZ:F3}) — grounding to the walkable, not through it")); + targetZ = bestWalkZ.Value; + } + } } }