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