fix(render): R1 — repurpose the ParentCellId==null cell-gate bypass (#78)
EntityPassesVisibleCellGate no longer returns true unconditionally for outdoor scenery under a cell filter (was the headline #78 bleed). Outdoor scenery now draws only via the unfiltered bucket (visibleCellIds: null) + ResolveEntitySlot's OutsideView routing. The outdoor-root global Draw passes visibleCellIds: null (no portal-cell scoping outdoors; retires VisibleCellIds as a render gate — peering into buildings is R5). Updated the EntityClipTests case that pinned the old bypass (Included -> Excluded). 174/174 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c4fd71149a
commit
58822fed96
3 changed files with 21 additions and 14 deletions
|
|
@ -7584,10 +7584,14 @@ public sealed class GameWindow : IDisposable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Outdoor root: the global entity pass (unchanged).
|
// Outdoor root: draw the full outdoor world. No cell filter — outdoors there is no
|
||||||
|
// portal-cell scoping (ClearClipRouting made every instance slot 0). R1 retires
|
||||||
|
// visibility.VisibleCellIds as a render gate (peering into buildings is R5, a
|
||||||
|
// separate pass). On the outdoor root visibility is null anyway, so this is the
|
||||||
|
// same set the old code passed; null makes that explicit + gate-change-safe.
|
||||||
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
|
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
|
||||||
neverCullLandblockId: playerLb,
|
neverCullLandblockId: playerLb,
|
||||||
visibleCellIds: visibility?.VisibleCellIds,
|
visibleCellIds: null,
|
||||||
animatedEntityIds: animatedIds);
|
animatedEntityIds: animatedIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1741,19 +1741,20 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
HashSet<uint>? visibleCellIds,
|
HashSet<uint>? visibleCellIds,
|
||||||
EntitySet set)
|
EntitySet set)
|
||||||
{
|
{
|
||||||
|
// No cell filter (outdoor root, or a bucket drawn unfiltered like live-dynamics / outdoor
|
||||||
|
// scenery) ⇒ every entity passes; clip-slot routing (ResolveEntitySlot) does the gating.
|
||||||
if (visibleCellIds is null)
|
if (visibleCellIds is null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// A cell-membership filter is active. An interior static passes iff its cell is visible.
|
||||||
if (entity.ParentCellId.HasValue)
|
if (entity.ParentCellId.HasValue)
|
||||||
return visibleCellIds.Contains(entity.ParentCellId.Value);
|
return visibleCellIds.Contains(entity.ParentCellId.Value);
|
||||||
|
|
||||||
if (IsShellScopedSet(set) && entity.IsBuildingShell)
|
// ParentCellId == null (outdoor scenery / building shell): NOT a member of any interior cell,
|
||||||
{
|
// so it does NOT pass a cell-membership filter (R1: the bleed fix — was an unconditional
|
||||||
return entity.BuildingShellAnchorCellId is uint anchorCellId
|
// `return true`). When such entities must draw (through the doorway), the caller passes
|
||||||
&& visibleCellIds.Contains(anchorCellId);
|
// visibleCellIds: null and relies on ResolveEntitySlot's OutsideView routing instead.
|
||||||
}
|
return false;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase U.1 (2026-05-30): the shell-scoped sets (IndoorPass / BuildingShells)
|
// Phase U.1 (2026-05-30): the shell-scoped sets (IndoorPass / BuildingShells)
|
||||||
|
|
|
||||||
|
|
@ -85,17 +85,19 @@ public sealed class EntityClipTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EntityClip_NullParentCell_NonNullVisibleSet_Included()
|
public void EntityClip_NullParentCell_NonNullVisibleSet_Excluded()
|
||||||
{
|
{
|
||||||
// An outdoor entity (ParentCellId == null) with a non-null visibleCellIds
|
// R1 (bleed fix #78): an outdoor entity (ParentCellId == null) with a non-null cell filter
|
||||||
// falls through to the final return-true (not a shell, not shell-scoped);
|
// does NOT pass — it is not a member of any interior cell. (Was an unconditional return-true
|
||||||
// outdoor scenery is not gated by the indoor cell filter.
|
// bypass, the headline outdoor-scenery bleed.) When such entities must draw through the
|
||||||
|
// doorway, the caller passes visibleCellIds: null and the OutsideView clip-slot routing
|
||||||
|
// (ResolveEntitySlot) gates them instead.
|
||||||
var visibleCellIds = new HashSet<uint> { 0xA9B40170u };
|
var visibleCellIds = new HashSet<uint> { 0xA9B40170u };
|
||||||
var entity = Entity(parentCellId: null);
|
var entity = Entity(parentCellId: null);
|
||||||
|
|
||||||
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
||||||
entity, visibleCellIds, WbDrawDispatcher.EntitySet.All);
|
entity, visibleCellIds, WbDrawDispatcher.EntitySet.All);
|
||||||
|
|
||||||
Assert.True(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue