diff --git a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
index b0ff7a6..b9fb084 100644
--- a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
+++ b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs
@@ -109,36 +109,57 @@ public sealed class ShadowObjectRegistry
}
///
- /// Get all objects in cells that a sphere at worldPos with given radius overlaps.
- /// This is the main query used by the Transition system.
+ /// Get all objects near a world position. Searches the given landblock plus
+ /// all 8 adjacent landblocks to handle objects near cell/landblock boundaries.
+ /// Within each landblock, queries only the cells the query sphere overlaps.
///
public void GetNearbyObjects(Vector3 worldPos, float queryRadius,
float worldOffsetX, float worldOffsetY, uint landblockId,
List results)
{
results.Clear();
- float localX = worldPos.X - worldOffsetX;
- float localY = worldPos.Y - worldOffsetY;
+ var seen = new HashSet();
- int minCx = Math.Max(0, (int)((localX - queryRadius) / 24f));
- int maxCx = Math.Min(7, (int)((localX + queryRadius) / 24f));
- int minCy = Math.Max(0, (int)((localY - queryRadius) / 24f));
- int maxCy = Math.Min(7, (int)((localY + queryRadius) / 24f));
+ // Extract landblock X/Y from the ID.
+ int lbX = (int)((landblockId >> 24) & 0xFF);
+ int lbY = (int)((landblockId >> 16) & 0xFF);
- uint lbPrefix = landblockId & 0xFFFF0000u;
- var seen = new HashSet(); // avoid duplicates from overlapping cells
-
- for (int cx = minCx; cx <= maxCx; cx++)
+ // Search the player's landblock and all 8 neighbors.
+ for (int dx = -1; dx <= 1; dx++)
{
- for (int cy = minCy; cy <= maxCy; cy++)
+ for (int dy = -1; dy <= 1; dy++)
{
- uint cellId = lbPrefix | (uint)(cx * 8 + cy + 1);
- if (!_cells.TryGetValue(cellId, out var list)) continue;
+ int nx = lbX + dx;
+ int ny = lbY + dy;
+ if (nx < 0 || nx > 255 || ny < 0 || ny > 255) continue;
- foreach (var entry in list)
+ uint neighborLb = ((uint)nx << 24) | ((uint)ny << 16) | 0xFFFFu;
+ uint nbPrefix = neighborLb & 0xFFFF0000u;
+
+ // Compute local position relative to this neighbor landblock.
+ float nbOffX = worldOffsetX + dx * 192f;
+ float nbOffY = worldOffsetY + dy * 192f;
+ float localX = worldPos.X - nbOffX;
+ float localY = worldPos.Y - nbOffY;
+
+ int minCx = Math.Max(0, (int)((localX - queryRadius) / 24f));
+ int maxCx = Math.Min(7, (int)((localX + queryRadius) / 24f));
+ int minCy = Math.Max(0, (int)((localY - queryRadius) / 24f));
+ int maxCy = Math.Min(7, (int)((localY + queryRadius) / 24f));
+
+ for (int cx = minCx; cx <= maxCx; cx++)
{
- if (seen.Add(entry.EntityId))
- results.Add(entry);
+ for (int cy = minCy; cy <= maxCy; cy++)
+ {
+ uint cellId = nbPrefix | (uint)(cx * 8 + cy + 1);
+ if (!_cells.TryGetValue(cellId, out var list)) continue;
+
+ foreach (var entry in list)
+ {
+ if (seen.Add(entry.EntityId))
+ results.Add(entry);
+ }
+ }
}
}
}
diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs
index 8913c85..7eb99fb 100644
--- a/src/AcDream.Core/Physics/TransitionTypes.cs
+++ b/src/AcDream.Core/Physics/TransitionTypes.cs
@@ -611,6 +611,7 @@ public sealed class Transition
// Reused per-call to avoid per-step allocation; safe because Transition
// is single-threaded per movement resolve.
private readonly List _nearbyObjs = new();
+ private int _debugMissCounter;
///
/// Query the ShadowObjectRegistry for nearby static objects and run
@@ -646,6 +647,9 @@ public sealed class Transition
worldOffsetX, worldOffsetY, landblockId,
_nearbyObjs);
+ if (_debugMissCounter % 600 == 0)
+ Console.WriteLine($"QUERY: pos={currPos} lb=0x{landblockId:X8} off=({worldOffsetX:F0},{worldOffsetY:F0}) qr={queryRadius:F1} found={_nearbyObjs.Count} totalReg={engine.ShadowObjects.TotalRegistered}");
+
// Find the EARLIEST collision along the movement path.
float bestT = float.MaxValue;
Vector3 bestNormal = Vector3.Zero;
@@ -750,7 +754,19 @@ public sealed class Transition
}
if (bestT >= float.MaxValue)
- return TransitionState.OK; // no collision
+ {
+ // Debug: log nearby objects that didn't trigger collision
+ if (_debugMissCounter++ % 120 == 0)
+ {
+ foreach (var obj in _nearbyObjs)
+ {
+ float d = Vector3.Distance(checkPos, obj.Position);
+ if (d < 3f)
+ Console.WriteLine($"NEAR: obj {obj.EntityId:X8} gfx={obj.GfxObjId:X8} type={obj.CollisionType} dist={d:F2} r={obj.Radius:F2} cylH={obj.CylHeight:F2} pos={obj.Position}");
+ }
+ }
+ return TransitionState.OK;
+ }
// Already overlapping at the START of the step (bestT == 0 or very small).
// This happens when the player spawns inside an object or a previous