acdream/tests/AcDream.App.Tests/World/LiveEntityLivenessControllerTests.cs
Erik 3971997689 fix(streaming): retire stale portal-region entities
Port retail's 25-second leave-visibility lifetime over canonical live records, retaining spatially resident and owned entities while using the conservative ACE visibility envelope for nonresident records. Route expiry through the normal generation-safe F747 teardown so animations, effects, physics, and render owners unwind symmetrically.

Replace the append-only modern mesh buffer with coalescing vertex/index ranges and upload each mesh's vertices once instead of once per material. Released zero-reference meshes can now reuse GPU ranges after portal and cache churn.

A connected five-region round trip returned animation ownership to baseline, recreated the starting region on revisit, and held normal FPS. Release build succeeds and all 5,927 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-18 10:02:15 +02:00

91 lines
3.4 KiB
C#

using AcDream.App.World;
using AcDream.Core.Net.Messages;
namespace AcDream.App.Tests.World;
public sealed class LiveEntityLivenessControllerTests
{
[Fact]
public void OutOfRangeWorldEntityExpiresAfterTwentyFiveSeconds()
{
var tracker = new LiveEntityLivenessTracker();
var samples = new[] { Sample(0x7000_0001u, generation: 4, visible: false) };
Assert.Empty(tracker.Tick(10.0, samples));
Assert.Empty(tracker.Tick(34.999, samples));
Assert.Equal(
new LiveEntityPruneCandidate(0x7000_0001u, 4),
Assert.Single(tracker.Tick(35.0, samples)));
Assert.Equal(0, tracker.DeadlineCount);
}
[Fact]
public void ReturningToVisibilityCancelsTheDeadline()
{
var tracker = new LiveEntityLivenessTracker();
Assert.Empty(tracker.Tick(0.0, [Sample(1, 1, visible: false)]));
Assert.Empty(tracker.Tick(20.0, [Sample(1, 1, visible: true)]));
Assert.Empty(tracker.Tick(40.0, [Sample(1, 1, visible: false)]));
Assert.Empty(tracker.Tick(64.9, [Sample(1, 1, visible: false)]));
Assert.Single(tracker.Tick(65.0, [Sample(1, 1, visible: false)]));
}
[Fact]
public void NonWorldRetentionCancelsTheDeadline()
{
var tracker = new LiveEntityLivenessTracker();
Assert.Empty(tracker.Tick(0.0, [Sample(1, 1, visible: false)]));
Assert.Empty(tracker.Tick(30.0, [Sample(1, 1, visible: false, retained: true)]));
Assert.Equal(0, tracker.DeadlineCount);
}
[Fact]
public void ReusedGuidGetsANewGenerationDeadline()
{
var tracker = new LiveEntityLivenessTracker();
Assert.Empty(tracker.Tick(0.0, [Sample(1, 1, visible: false)]));
Assert.Empty(tracker.Tick(24.0, [Sample(1, 2, visible: false)]));
Assert.Empty(tracker.Tick(25.0, [Sample(1, 2, visible: false)]));
Assert.Equal(
new LiveEntityPruneCandidate(1, 2),
Assert.Single(tracker.Tick(49.0, [Sample(1, 2, visible: false)])));
}
[Fact]
public void RemovedRecordDropsItsDeadline()
{
var tracker = new LiveEntityLivenessTracker();
Assert.Empty(tracker.Tick(0.0, [Sample(1, 1, visible: false)]));
Assert.Empty(tracker.Tick(30.0, []));
Assert.Equal(0, tracker.DeadlineCount);
}
[Fact]
public void ConservativeVisibilityUsesGlobalLandblockCoordinates()
{
CreateObject.ServerPosition player = Position(0x3032_0001u, 190f, 20f, 5f);
CreateObject.ServerPosition adjacent = Position(0x3132_0001u, 1f, 20f, 5f);
CreateObject.ServerPosition exactlyTwoBlocks = Position(0x3232_0001u, 190f, 20f, 5f);
CreateObject.ServerPosition beyond = Position(0x3332_0001u, 1f, 20f, 5f);
Assert.True(LiveEntityLivenessController.IsWithinConservativeVisibility(player, adjacent));
Assert.True(LiveEntityLivenessController.IsWithinConservativeVisibility(player, exactlyTwoBlocks));
Assert.False(LiveEntityLivenessController.IsWithinConservativeVisibility(player, beyond));
}
private static LiveEntityLivenessSample Sample(
uint guid,
ushort generation,
bool visible,
bool retained = false) =>
new(guid, generation, visible, retained);
private static CreateObject.ServerPosition Position(
uint cell,
float x,
float y,
float z) =>
new(cell, x, y, z, 1f, 0f, 0f, 0f);
}