fix(physics): #91 — query indoor cell shadows in FindObjCollisions
Interior items (fireplaces, tables, chests) registered via A1.5's
ShadowObjectRegistry.Register `cellScope` parameter (commit 4d3bf6f)
are stored under their ParentCellId key (e.g. 0xA9B40121). But
GetNearbyObjects's broad-phase only iterates outdoor 24m landcell
keys (0xA9B40029 etc) and never looks up indoor cell keys, so
interior shadows were registered but unreachable. User-visible
symptom: tables/boxes/fireplaces don't block movement, while walls
DO block (the indoor BSP path is separate).
Fix: GetNearbyObjects accepts an optional indoorCellIds parameter
and additionally queries _cells[indoorCellId] for each entry with
low-byte >= 0x0100u. FindObjCollisions computes the set via
CellTransit.FindCellSet (same set A4 uses for multi-cell BSP
iteration) and passes it through. Outdoor seeds typically produce
sets containing only outdoor land-cells which the new branch filters
out, so the outdoor-only behavior is preserved.
1147 + 8 baseline maintained. Closes the user-reported regression
"walls block now correct but interior items such as tables and boxes
or fireplaces do not block."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4ca35966f8
commit
c0d84057cb
2 changed files with 50 additions and 2 deletions
|
|
@ -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.
|
||||
///
|
||||
/// <para>
|
||||
/// Issue #91 (2026-05-20): the optional <paramref name="indoorCellIds"/>
|
||||
/// parameter is the candidate set of indoor cells the foot-sphere overlaps
|
||||
/// (from <see cref="CellTransit.FindCellSet"/>). When supplied, indoor
|
||||
/// shadows registered via <see cref="Register"/>'s <c>cellScope</c>
|
||||
/// 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.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void GetNearbyObjects(Vector3 worldPos, float queryRadius,
|
||||
float worldOffsetX, float worldOffsetY, uint landblockId,
|
||||
List<ShadowEntry> results)
|
||||
List<ShadowEntry> results,
|
||||
System.Collections.Generic.IReadOnlyCollection<uint>? indoorCellIds = null)
|
||||
{
|
||||
results.Clear();
|
||||
var seen = new HashSet<uint>();
|
||||
|
||||
// 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);
|
||||
|
|
|
|||
|
|
@ -1911,10 +1911,29 @@ public sealed class Transition
|
|||
// iteration. Allocate per call (cheap — typically 0-5 entries).
|
||||
var nearbyObjs = new List<ShadowEntry>();
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue