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; + } + } } }