diff --git a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs index 9c52435..b574591 100644 --- a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs +++ b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs @@ -242,14 +242,43 @@ public sealed class ShadowObjectRegistry /// 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. + /// + /// + /// Issue #91 (2026-05-20): the optional + /// parameter is the candidate set of indoor cells the foot-sphere overlaps + /// (from ). When supplied, indoor + /// shadows registered via 's cellScope + /// parameter (A1.5 fix at `4d3bf6f`) are ALSO included in the result. + /// Without this, interior statics (fireplaces, tables, chests) registered + /// against e.g. `0xA9B40121` are stored under that key but the outdoor- + /// grid lookup (cell ids like `0xA9B40029`) never queries the indoor key. + /// Net effect pre-fix: interior items don't block movement. + /// /// public void GetNearbyObjects(Vector3 worldPos, float queryRadius, float worldOffsetX, float worldOffsetY, uint landblockId, - List results) + List results, + System.Collections.Generic.IReadOnlyCollection? indoorCellIds = null) { results.Clear(); var seen = new HashSet(); + // Indoor-scoped shadows (A1.5 cellScope). Query first so the + // outdoor-grid lookup below skips duplicates via `seen`. + if (indoorCellIds is not null) + { + foreach (uint indoorCellId in indoorCellIds) + { + if ((indoorCellId & 0xFFFFu) < 0x0100u) continue; // skip outdoor ids + if (!_cells.TryGetValue(indoorCellId, out var list)) continue; + foreach (var entry in list) + { + if (seen.Add(entry.EntityId)) + results.Add(entry); + } + } + } + // Extract landblock X/Y from the ID. int lbX = (int)((landblockId >> 24) & 0xFF); int lbY = (int)((landblockId >> 16) & 0xFF); diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index e5f1e86..dceaab8 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -1911,10 +1911,29 @@ public sealed class Transition // iteration. Allocate per call (cheap — typically 0-5 entries). var nearbyObjs = new List(); float queryRadius = sphereRadius + movement.Length() + 5f; + + // Issue #91 (2026-05-20): interior items (fireplaces, tables, chests) + // are registered with `cellScope = ParentCellId` per A1.5's fix at + // `4d3bf6f`. They're stored under the indoor cell key (e.g. + // `0xA9B40121`), but GetNearbyObjects's outdoor-grid lookup (cells + // like `0xA9B40029`) never queries that key. Compute the indoor + // candidate set via CellTransit.FindCellSet — same set A4 uses for + // multi-cell BSP iteration — and pass it through so indoor shadows + // are also picked up. When the seed is outdoor the set typically + // contains only outdoor land-cells which the new branch in + // GetNearbyObjects skips via the `< 0x0100u` filter, so behavior + // matches the prior outdoor-only path. + // (engine.DataCache is non-null per the early-return at top of + // FindObjCollisions; redundant inner check would confuse nullable + // flow analysis.) + _ = CellTransit.FindCellSet(engine.DataCache, currPos, sphereRadius, + sp.CheckCellId, out var indoorCellIds); + engine.ShadowObjects.GetNearbyObjects( currPos, queryRadius, worldOffsetX, worldOffsetY, landblockId, - nearbyObjs); + nearbyObjs, + indoorCellIds); foreach (var obj in nearbyObjs) {