fix(physics): scope interior cell shadows to ParentCellId (Phase A1.5)
ISSUES #83 Phase A1.5. ShadowObjectRegistry.Register() assigned each entity to the outdoor landcell grid (8x8 cells, 24m square) based on its XY position. For interior EnvCell statics (fireplace, furniture, sign) hydrated by BuildInteriorEntitiesForStreaming with ParentCellId = envCellId (a high-cellId interior cell like 0xA9B40121), this meant the shadow got stamped into the OUTDOOR landcell whose XY they overlapped (e.g., 0xA9B40029). When the player was OUTSIDE the building in 0xA9B40029, the indoor chair/fireplace shadow fired collisions in "thin air" outdoors. The user reported this on Holtburg cottage exteriors after the Phase A1 landblock-stab fallback fix. Fix: add optional cellScope parameter to Register(). When non-zero (passed as entity.ParentCellId ?? 0u from the 5 entity-loop call sites in GameWindow), skip the XY-based landcell loop and register the shadow ONLY in that cell. Live server-spawn registration at GameWindow.cs:3137 keeps the XY-based behavior (live entities move between cells). Probe evidence (launch-a1-verify.utf8.log, post-A1 capture): - 71 hits on 0x40B50054 (interior static) in OUTDOOR cell 0xA9B40029. - 47 hits on 0xA9B47C00 (other Holtburg cottage BSP — legitimate). - 31 hits on 0x40B50048 / 15 on 0x40B50018 (interior statics). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f2b545979
commit
4d3bf6fe37
2 changed files with 35 additions and 8 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<ShadowEntry>();
|
||||
_cells[cellScope] = scopedList;
|
||||
}
|
||||
scopedList.Add(entry);
|
||||
_entityToCells[entityId] = new List<uint> { 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>();
|
||||
|
||||
uint lbPrefix = landblockId & 0xFFFF0000u;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue