diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 37e5087..4d5f26c 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5887,7 +5887,8 @@ public sealed class GameWindow : IDisposable partPos, partRot, worldRadius, origin.X, origin.Y, lb.LandblockId, AcDream.Core.Physics.ShadowCollisionType.BSP, 0f, - partScale); + partScale, + cellScope: entity.ParentCellId ?? 0u); // L.2d slice 1 (2026-05-13): [entity-source] greppable from [resolve-bldg]. // partCached?.BSP?.Root non-null was checked above (else `continue`), // so hasPhys=true on this path. @@ -5946,7 +5947,8 @@ public sealed class GameWindow : IDisposable entity.Position + worldOffset, entity.Rotation, cylRadius, origin.X, origin.Y, lb.LandblockId, - AcDream.Core.Physics.ShadowCollisionType.Cylinder, cylHeight); + AcDream.Core.Physics.ShadowCollisionType.Cylinder, cylHeight, + cellScope: entity.ParentCellId ?? 0u); // L.2d slice 1 (2026-05-13): [entity-source] greppable from [resolve-bldg]. // state/flags literals: landblock-baked scenery; no server PhysicsState. if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled) @@ -5981,7 +5983,8 @@ public sealed class GameWindow : IDisposable entity.Position + worldOffset, entity.Rotation, sphRadius, origin.X, origin.Y, lb.LandblockId, - AcDream.Core.Physics.ShadowCollisionType.Cylinder, sphHeight); + AcDream.Core.Physics.ShadowCollisionType.Cylinder, sphHeight, + cellScope: entity.ParentCellId ?? 0u); // L.2d slice 1 (2026-05-13): [entity-source] greppable from [resolve-bldg]. // state/flags literals: landblock-baked scenery; no server PhysicsState. if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled) @@ -6004,7 +6007,8 @@ public sealed class GameWindow : IDisposable shapeId, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, fr, origin.X, origin.Y, lb.LandblockId, - AcDream.Core.Physics.ShadowCollisionType.Cylinder, fh); + AcDream.Core.Physics.ShadowCollisionType.Cylinder, fh, + cellScope: entity.ParentCellId ?? 0u); // L.2d slice 1 (2026-05-13): [entity-source] greppable from [resolve-bldg]. // state/flags literals: landblock-baked scenery; no server PhysicsState. if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled) @@ -6191,7 +6195,8 @@ public sealed class GameWindow : IDisposable entity.SourceGfxObjOrSetupId, baseCenter, entity.Rotation, cylRadius, origin.X, origin.Y, lb.LandblockId, - AcDream.Core.Physics.ShadowCollisionType.Cylinder, cylHeight); + AcDream.Core.Physics.ShadowCollisionType.Cylinder, cylHeight, + cellScope: entity.ParentCellId ?? 0u); // L.2d slice 1 (2026-05-13): [entity-source] greppable from [resolve-bldg]. // state/flags literals: landblock-baked scenery; no server PhysicsState. if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled) diff --git a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs index fd3673e..9c52435 100644 --- a/src/AcDream.Core/Physics/ShadowObjectRegistry.cs +++ b/src/AcDream.Core/Physics/ShadowObjectRegistry.cs @@ -36,10 +36,34 @@ public sealed class ShadowObjectRegistry ShadowCollisionType collisionType = ShadowCollisionType.BSP, float cylHeight = 0f, float scale = 1.0f, uint state = 0u, - EntityCollisionFlags flags = EntityCollisionFlags.None) + EntityCollisionFlags flags = EntityCollisionFlags.None, + uint cellScope = 0u) { Deregister(entityId); + var entry = new ShadowEntry(entityId, gfxObjId, worldPos, rotation, radius, + collisionType, cylHeight, scale, state, flags); + + // ISSUES #83 / Phase A1.5 (2026-05-21): if the caller passed a + // cellScope (typically the entity's ParentCellId for an interior + // EnvCell static), scope the shadow to ONLY that cell instead of + // computing outdoor-landcell occupancy from XY. Without this, + // interior statics (a fireplace inside cell 0xA9B40121) get + // registered into the outdoor landcell whose XY they overlap + // (e.g. 0xA9B40029) and fire collisions when the player is OUTSIDE + // the building — the user-reported "thin air" collision outdoors. + if (cellScope != 0u) + { + if (!_cells.TryGetValue(cellScope, out var scopedList)) + { + scopedList = new List(); + _cells[cellScope] = scopedList; + } + scopedList.Add(entry); + _entityToCells[entityId] = new List { cellScope }; + return; + } + // The radius parameter should already be the WORLD-SPACE bounding // radius (i.e., already multiplied by scale) so the broad-phase cell // occupancy is correct. Callers are responsible for that. @@ -51,8 +75,6 @@ public sealed class ShadowObjectRegistry int minCy = Math.Max(0, (int)((localY - radius) / 24f)); int maxCy = Math.Min(7, (int)((localY + radius) / 24f)); - var entry = new ShadowEntry(entityId, gfxObjId, worldPos, rotation, radius, - collisionType, cylHeight, scale, state, flags); var cellIds = new List(); uint lbPrefix = landblockId & 0xFFFF0000u;