diff --git a/src/AcDream.Core/Physics/PhysicsDataCache.cs b/src/AcDream.Core/Physics/PhysicsDataCache.cs
index d0876495..f90808fa 100644
--- a/src/AcDream.Core/Physics/PhysicsDataCache.cs
+++ b/src/AcDream.Core/Physics/PhysicsDataCache.cs
@@ -459,6 +459,24 @@ public sealed class PhysicsDataCache
_buildings.TryRemove(key, out _);
}
+ ///
+ /// D8 (2026-06-24): drop every cached cell struct belonging to a landblock so
+ /// the next re-bases its STREAMING-RELATIVE
+ /// WorldTransform against the current _liveCenter — symmetric with
+ /// (#146). Without this, the
+ /// _cellStruct first-wins guard LOCKS a dungeon cell's transform at its
+ /// first streaming frame; a teleport recenter then leaves the cell BSP at a stale
+ /// world offset (~the source↔dest landblock distance), so foot-sphere collision
+ /// queries walk through where the wall geometry is.
+ ///
+ public void RemoveCellsForLandblock(uint landblockId)
+ {
+ uint prefix = landblockId & 0xFFFF0000u;
+ foreach (var key in _cellStruct.Keys)
+ if ((key & 0xFFFF0000u) == prefix)
+ _cellStruct.TryRemove(key, out _);
+ }
+
public BuildingPhysics? GetBuilding(uint landcellId)
=> _buildings.TryGetValue(landcellId, out var b) ? b : null;
diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs
index e240fec0..1090559e 100644
--- a/src/AcDream.Core/Physics/PhysicsEngine.cs
+++ b/src/AcDream.Core/Physics/PhysicsEngine.cs
@@ -122,6 +122,7 @@ public sealed class PhysicsEngine
{
_landblocks.Remove(landblockId);
ShadowObjects.RemoveLandblock(landblockId);
+ DataCache?.RemoveCellsForLandblock(landblockId); // D8: rebase cell BSP transforms on next apply
// #145: if the player's current cell belonged to the landblock being removed (a teleport
// drops the stale source center via OnLivePositionUpdated), clear it. Otherwise CurrCell
diff --git a/tests/AcDream.Core.Tests/Physics/RemoveCellsForLandblockTests.cs b/tests/AcDream.Core.Tests/Physics/RemoveCellsForLandblockTests.cs
new file mode 100644
index 00000000..bd12f7c8
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Physics/RemoveCellsForLandblockTests.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using AcDream.Core.Physics;
+using Xunit;
+
+namespace AcDream.Core.Tests.Physics;
+
+///
+/// D8 (2026-06-24): verify that
+/// evicts every cached cell belonging to the given landblock prefix and leaves
+/// cells belonging to other landblocks untouched — symmetric with
+/// (#146).
+///
+public class RemoveCellsForLandblockTests
+{
+ ///
+ /// Build the absolute minimum that satisfies the
+ /// required init property. BSP is left null because the test exercises
+ /// only the cache-eviction key logic, not BSP traversal.
+ ///
+ private static CellPhysics BuildMinimalCell() => new()
+ {
+ Resolved = new Dictionary(),
+ };
+
+ [Fact]
+ public void RemoveCellsForLandblock_EvictsOnlyMatchingPrefix()
+ {
+ var cache = new PhysicsDataCache();
+
+ // Two cells from landblock 0xAAAA0000 and one from 0xBBBB0000.
+ cache.RegisterCellStructForTest(0xAAAA0100u, BuildMinimalCell());
+ cache.RegisterCellStructForTest(0xAAAA0200u, BuildMinimalCell());
+ cache.RegisterCellStructForTest(0xBBBB0100u, BuildMinimalCell());
+
+ cache.RemoveCellsForLandblock(0xAAAA0000u);
+
+ // Both AAAA cells must be gone.
+ Assert.Null(cache.GetCellStruct(0xAAAA0100u));
+ Assert.Null(cache.GetCellStruct(0xAAAA0200u));
+
+ // The BBBB cell must survive.
+ Assert.NotNull(cache.GetCellStruct(0xBBBB0100u));
+ }
+
+ [Fact]
+ public void RemoveCellsForLandblock_EmptyCache_DoesNotThrow()
+ {
+ // Must be a no-op on an empty cache.
+ var cache = new PhysicsDataCache();
+ cache.RemoveCellsForLandblock(0xAAAA0000u);
+ Assert.Equal(0, cache.CellStructCount);
+ }
+
+ [Fact]
+ public void RemoveCellsForLandblock_NoMatchingCells_LeavesOthersIntact()
+ {
+ var cache = new PhysicsDataCache();
+ cache.RegisterCellStructForTest(0xBBBB0100u, BuildMinimalCell());
+
+ // Evicting a prefix that has no cached entries should not disturb others.
+ cache.RemoveCellsForLandblock(0xAAAA0000u);
+
+ Assert.NotNull(cache.GetCellStruct(0xBBBB0100u));
+ Assert.Equal(1, cache.CellStructCount);
+ }
+}