fix(render): keep animated entities visible when their landblock is frustum-culled

User report: other characters disappear when the camera rotates,
even though they're standing within visible distance.

Root cause: InstancedMeshRenderer's landblock-level frustum cull
(InstancedMeshRenderer.cs:352-355) skipped the entire landblock's
entity list when the landblock AABB was outside the frustum.
Static scenery culling that way is fine, but ANIMATED entities
(remote players, NPCs, monsters) got culled with the landblock --
they vanished as soon as the camera turned away from their block.

Fix: pass an animatedEntityIds set to Draw. Inside CollectGroups
the landblock-cull decision is now per-landblock boolean (not a
continue), and the per-entity loop bypasses the cull when the
entity id is in animatedEntityIds. Static entities still respect
the landblock cull. GameWindow rebuilds the set per frame from
_animatedEntities (typically <100 entities, cheap).

Fast path preserved: when animatedEntityIds is null/empty AND
the landblock is culled, skip the entity list entirely -- same
O(visible-landblocks) cost as before.

Tests stay 1439 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-29 11:10:04 +02:00
parent b93dfe95d8
commit 559b79dc98
2 changed files with 52 additions and 7 deletions

View file

@ -5023,9 +5023,26 @@ public sealed class GameWindow : IDisposable
if (cameraInsideCell)
_gl!.Clear(ClearBufferMask.DepthBufferBit);
// L-fix1 (2026-04-28): pass the set of animated-entity ids so
// the renderer keeps remote players / NPCs / monsters
// visible even when their landblock rotates out of the
// frustum. Without this, other characters wink in/out as
// the camera turns. The set is rebuilt per-frame from
// _animatedEntities — it's small (<100 entities typically)
// so HashSet allocation is cheap. Static scenery still
// respects landblock-level cull.
HashSet<uint>? animatedIds = null;
if (_animatedEntities.Count > 0)
{
animatedIds = new HashSet<uint>(_animatedEntities.Count);
foreach (var k in _animatedEntities.Keys)
animatedIds.Add(k);
}
_staticMesh?.Draw(camera, _worldState.LandblockEntries, frustum,
neverCullLandblockId: playerLb,
visibleCellIds: visibility?.VisibleCellIds);
visibleCellIds: visibility?.VisibleCellIds,
animatedEntityIds: animatedIds);
// Phase G.1 / E.3: draw all live particles after opaque
// scene geometry so alpha blending composites correctly.