acdream/tests/AcDream.App.Tests/Rendering/CurrentRenderSceneOracleTests.cs
Erik b2b67341ac feat(rendering): establish current-path scene referee
Capture deterministic, landblock-aware fingerprints from the accepted partition only during lifecycle automation. This gives the F/G shadow scene an independent oracle without changing normal draw decisions or adding disabled-path per-entity cost.
2026-07-24 21:13:11 +02:00

280 lines
9.8 KiB
C#

using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
public sealed class CurrentRenderSceneOracleTests
{
private const uint LandblockA = 0xA9B4FFFF;
private const uint LandblockB = 0xA9B5FFFF;
private const uint CellA = 0xA9B40170;
private const uint CellB = 0xA9B50170;
[Fact]
public void DigestIsStableAcrossLandblockEnumerationOrder()
{
WorldEntity outdoorA = Entity(id: 7, serverGuid: 0, parentCell: null);
WorldEntity cellStatic = Entity(id: 8, serverGuid: 0, parentCell: CellA);
WorldEntity live = Entity(
id: 1_000_001,
serverGuid: 0x8000_0001,
parentCell: CellA);
// Static IDs are landblock-local. The same id in another landblock is
// a distinct projection and must survive the referee sort.
WorldEntity outdoorB = Entity(id: 7, serverGuid: 0, parentCell: null);
var firstEntries = new[]
{
Entry(LandblockA, outdoorA, cellStatic, live),
Entry(LandblockB, outdoorB),
};
var secondEntries = new[]
{
Entry(LandblockB, outdoorB),
Entry(LandblockA, outdoorA, cellStatic, live),
};
var visible = new HashSet<uint> { CellA };
var result = new InteriorEntityPartition.Result();
var oracle = new CurrentRenderSceneOracle();
InteriorEntityPartition.Partition(
result,
visible,
firstEntries,
oracle);
CurrentRenderSceneOracleSnapshot first = oracle.Snapshot;
InteriorEntityPartition.Partition(
result,
visible,
secondEntries,
oracle);
CurrentRenderSceneOracleSnapshot second = oracle.Snapshot;
Assert.True(first.Enabled);
Assert.Equal(4, first.ProjectionCount);
Assert.Equal(2, first.OutdoorStaticCount);
Assert.Equal(1, first.CellStaticCount);
Assert.Equal(1, first.DynamicCount);
Assert.Equal(1, first.CellBucketCount);
Assert.Equal(first.Digest, second.Digest);
Assert.Equal(first.CompletedFrameSequence + 1, second.CompletedFrameSequence);
Assert.Equal(2, oracle.Projections.Count(
projection => projection.EntityId == 7));
Assert.Contains(
oracle.Projections,
projection => projection.EntityId == 7
&& projection.LandblockId == LandblockA);
Assert.Contains(
oracle.Projections,
projection => projection.EntityId == 7
&& projection.LandblockId == LandblockB);
}
[Fact]
public void ObservedPartitionMatchesUninstrumentedProductionPath()
{
WorldEntity outdoor = Entity(id: 10, serverGuid: 0, parentCell: null);
WorldEntity cellStatic = Entity(id: 11, serverGuid: 0, parentCell: CellA);
WorldEntity dynamic = Entity(
id: 1_000_011,
serverGuid: 0x8000_0011,
parentCell: CellB);
WorldEntity hiddenStatic = Entity(
id: 12,
serverGuid: 0,
parentCell: CellB);
var entries = new[]
{
Entry(LandblockA, outdoor, cellStatic),
Entry(LandblockB, dynamic, hiddenStatic),
};
var visible = new HashSet<uint> { CellA };
var production = new InteriorEntityPartition.Result();
var observed = new InteriorEntityPartition.Result();
var oracle = new CurrentRenderSceneOracle();
InteriorEntityPartition.Partition(production, visible, entries);
InteriorEntityPartition.Partition(observed, visible, entries, oracle);
Assert.Equal(production.ByCell.Keys, observed.ByCell.Keys);
foreach (uint cell in production.ByCell.Keys)
Assert.Equal(production.ByCell[cell], observed.ByCell[cell]);
Assert.Equal(production.OutdoorStatic, observed.OutdoorStatic);
Assert.Equal(production.Dynamics, observed.Dynamics);
}
[Fact]
public void FingerprintsNameTransformGeometryAppearanceAndFlagChanges()
{
var visible = new HashSet<uint> { CellA, CellB };
CurrentRenderProjectionFingerprint baseline = CaptureSingle(
Entity(id: 20, serverGuid: 0, parentCell: CellA),
LandblockA,
visible);
CurrentRenderProjectionFingerprint transform = CaptureSingle(
Entity(
id: 20,
serverGuid: 0,
parentCell: CellA,
position: new Vector3(1, 2, 3)),
LandblockA,
visible);
CurrentRenderProjectionFingerprint geometry = CaptureSingle(
Entity(
id: 20,
serverGuid: 0,
parentCell: CellA,
meshId: 0x0100_2222),
LandblockA,
visible);
CurrentRenderProjectionFingerprint appearance = CaptureSingle(
Entity(
id: 20,
serverGuid: 0,
parentCell: CellA,
palette: new PaletteOverride(
0x0400_0001,
[new PaletteOverride.SubPaletteRange(0x0400_0002, 3, 4)])),
LandblockA,
visible);
WorldEntity hidden = Entity(id: 20, serverGuid: 0, parentCell: CellA);
hidden.IsDrawVisible = false;
CurrentRenderProjectionFingerprint flags = CaptureSingle(
hidden,
LandblockA,
visible);
CurrentRenderProjectionFingerprint cell = CaptureSingle(
Entity(id: 20, serverGuid: 0, parentCell: CellB),
LandblockB,
visible);
Assert.NotEqual(baseline.Transform, transform.Transform);
Assert.Equal(baseline.Geometry, transform.Geometry);
Assert.Equal(baseline.Appearance, transform.Appearance);
Assert.NotEqual(baseline.Geometry, geometry.Geometry);
Assert.Equal(baseline.Transform, geometry.Transform);
Assert.NotEqual(baseline.Appearance, appearance.Appearance);
Assert.NotEqual(baseline.Flags, flags.Flags);
Assert.NotEqual(baseline.ParentCellId, cell.ParentCellId);
Assert.NotEqual(baseline.LandblockId, cell.LandblockId);
}
[Fact]
public void NonFloodedStaticIsAbsentButNonFloodedDynamicRemains()
{
WorldEntity hiddenStatic = Entity(
id: 30,
serverGuid: 0,
parentCell: CellB);
WorldEntity hiddenDynamic = Entity(
id: 1_000_030,
serverGuid: 0x8000_0030,
parentCell: CellB);
var oracle = new CurrentRenderSceneOracle();
var result = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
result,
new HashSet<uint> { CellA },
new[] { Entry(LandblockB, hiddenStatic, hiddenDynamic) },
oracle);
Assert.Equal(1, oracle.Snapshot.ProjectionCount);
Assert.Equal(0, oracle.Snapshot.CellStaticCount);
Assert.Equal(1, oracle.Snapshot.DynamicCount);
CurrentRenderProjectionFingerprint fingerprint =
Assert.Single(oracle.Projections);
Assert.Equal(hiddenDynamic.Id, fingerprint.EntityId);
Assert.Equal(
InteriorEntityPartition.ProjectionClass.Dynamic,
fingerprint.ProjectionClass);
}
[Fact]
public void AbortPreservesLastCompleteDigestAndRecordsFailure()
{
var oracle = new CurrentRenderSceneOracle();
var result = new InteriorEntityPartition.Result();
WorldEntity entity = Entity(id: 40, serverGuid: 0, parentCell: null);
InteriorEntityPartition.Partition(
result,
[],
new[] { Entry(LandblockA, entity) },
oracle);
CurrentRenderSceneOracleSnapshot complete = oracle.Snapshot;
oracle.BeginFrame();
oracle.Observe(
LandblockA,
entity,
InteriorEntityPartition.ProjectionClass.OutdoorStatic);
oracle.AbortFrame();
Assert.Equal(complete.Digest, oracle.Snapshot.Digest);
Assert.Equal(
complete.CompletedFrameSequence,
oracle.Snapshot.CompletedFrameSequence);
Assert.Equal(complete.AbortedFrames + 1, oracle.Snapshot.AbortedFrames);
Assert.Empty(oracle.Projections);
}
private static CurrentRenderProjectionFingerprint CaptureSingle(
WorldEntity entity,
uint landblockId,
HashSet<uint> visible)
{
var oracle = new CurrentRenderSceneOracle();
var result = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
result,
visible,
new[] { Entry(landblockId, entity) },
oracle);
return Assert.Single(oracle.Projections);
}
private static WorldEntity Entity(
uint id,
uint serverGuid,
uint? parentCell,
Vector3? position = null,
uint meshId = 0x0100_0001,
PaletteOverride? palette = null) =>
new()
{
Id = id,
ServerGuid = serverGuid,
SourceGfxObjOrSetupId = meshId,
Position = position ?? Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs =
[
new MeshRef(
meshId,
Matrix4x4.CreateTranslation(0.25f, 0.5f, 0.75f)),
],
ParentCellId = parentCell,
PaletteOverride = palette,
};
private static (
uint LandblockId,
Vector3 AabbMin,
Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)
Entry(uint landblockId, params WorldEntity[] entities) =>
(
landblockId,
Vector3.Zero,
Vector3.One,
entities,
null);
}