The first commit converted the four members the issue named and left the other sites alone, reasoning that none had been observed failing. A 20-run complete-solution baseline disproved that within minutes: run 2 LiveEntityRuntimeTests.AnimationView_HotSpatialTraversal… run 14 StaticRenderProjectionJournalTests.ActiveAnimatedSynchronization… run 18 StaticRenderProjectionJournalTests.ActiveAnimatedSynchronization… run 19 CurrentRenderSceneOracleTests.SurfaceOverrideFingerprint… Both new names are the same shape as the four — one warm call, then a thousand-iteration loop inside the measured window — and neither had been recorded anywhere. "Not observed failing" only ever meant "not yet observed", and leaving known-shape sites in place would have guaranteed the acceptance gate failed. Run 19 is the sharper lesson: the issue named `SurfaceOverrideFingerprint_DictionaryHotPathAllocatesNothing`, and the first commit converted a *different* test in that same file, so the actually-named member was still on the old shape. Matching by file was not matching by test. Every strict-zero site in the assembly is now on the probe — ten tests. Two came out stricter rather than merely steadier: `StaticRenderProjectionJournalTests` was measuring a synchronise whose journal does **not** coalesce. Repeating it grew the journal by 1,000 entries per call — 192,000 by the end of a probe run — so the steady state the test claimed to measure did not exist and the single-call window had been hiding it. Its step is now the whole frame cycle, synchronise *and* drain, which puts `DrainTo` inside the measured window for the first time and asserts the journal ends empty. `RetailInboundEventDispatcherTests` asserted a hard-coded 1,001 callbacks. It now counts its own dispatches and pins the callback count against that, so the assertion still proves the fast path ran the callback every time without being coupled to a loop bound that no longer exists. Left alone deliberately: the four sites asserting a tolerance rather than zero — `CellViewDedupTests` and `PortalProjectionTests`. Their ceilings already absorb this noise and none has flaked; changing a bound in either direction is a separate decision from fixing a measurement. Worth noting that `PortalProjectionTests`' ceiling exists explicitly to tolerate "a tiered-JIT/ArrayPool bookkeeping transition ... to the first measured batch", which is exactly what the probe removes, so it could probably be tightened to zero now — recorded in the issue rather than done here. Solution build 0 warnings / 0 errors; App suite 3,941 passed / 3 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
552 lines
19 KiB
C#
552 lines
19 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.Selection;
|
|
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);
|
|
}
|
|
|
|
[Fact]
|
|
public void PViewDigestNamesRouteOrderAndRejectsObjectsOutsidePartition()
|
|
{
|
|
WorldEntity cellStatic = Entity(
|
|
id: 50,
|
|
serverGuid: 0,
|
|
parentCell: CellA);
|
|
WorldEntity dynamic = Entity(
|
|
id: 1_000_050,
|
|
serverGuid: 0x8000_0050,
|
|
parentCell: CellA);
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
var result = new InteriorEntityPartition.Result();
|
|
InteriorEntityPartition.Partition(
|
|
result,
|
|
new HashSet<uint> { CellA },
|
|
new[] { Entry(LandblockA, cellStatic, dynamic) },
|
|
oracle);
|
|
|
|
oracle.BeginPViewFrame();
|
|
oracle.ObservePViewBucket(
|
|
CurrentRenderPViewRoute.CellStatic,
|
|
routeIndex: 0,
|
|
CellA,
|
|
[cellStatic]);
|
|
oracle.ObservePViewBucket(
|
|
CurrentRenderPViewRoute.DynamicLast,
|
|
routeIndex: 0,
|
|
cellId: 0,
|
|
[dynamic]);
|
|
oracle.CompletePViewFrame();
|
|
CurrentRenderSceneOracleSnapshot first = oracle.Snapshot;
|
|
|
|
oracle.BeginPViewFrame();
|
|
oracle.ObservePViewBucket(
|
|
CurrentRenderPViewRoute.DynamicLast,
|
|
routeIndex: 0,
|
|
cellId: 0,
|
|
[dynamic]);
|
|
oracle.ObservePViewBucket(
|
|
CurrentRenderPViewRoute.CellStatic,
|
|
routeIndex: 0,
|
|
CellA,
|
|
[cellStatic]);
|
|
oracle.CompletePViewFrame();
|
|
CurrentRenderSceneOracleSnapshot second = oracle.Snapshot;
|
|
|
|
Assert.Equal(2, first.PViewCandidateCount);
|
|
Assert.NotEqual(first.PViewDigest, second.PViewDigest);
|
|
Assert.Equal(
|
|
first.CompletedPViewFrameSequence + 1,
|
|
second.CompletedPViewFrameSequence);
|
|
|
|
oracle.BeginPViewFrame();
|
|
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
|
|
() => oracle.ObservePViewBucket(
|
|
CurrentRenderPViewRoute.CellStatic,
|
|
routeIndex: 0,
|
|
CellA,
|
|
[Entity(51, 0, CellA)]));
|
|
Assert.Contains("absent from the current partition", error.Message);
|
|
oracle.AbortPViewFrame();
|
|
}
|
|
|
|
[Fact]
|
|
public void DispatcherDigestResetsPerFrameAndRecordsAcceptedMeshRefs()
|
|
{
|
|
WorldEntity entity = Entity(
|
|
id: 60,
|
|
serverGuid: 0,
|
|
parentCell: CellA);
|
|
var accepted = new List<(
|
|
WorldEntity Entity,
|
|
int MeshRefIndex,
|
|
uint LandblockId)>
|
|
{
|
|
(entity, 0, LandblockB),
|
|
};
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
|
|
oracle.BeginDispatcherFrame();
|
|
oracle.ObserveDispatcherDraw(
|
|
WbDrawDispatcher.EntitySet.All,
|
|
entitiesWalked: 1,
|
|
accepted);
|
|
var submission = new CurrentRenderDispatcherSubmission(
|
|
VisibleInstanceCount: 3,
|
|
ImmediateInstanceCount: 2,
|
|
OpaqueGroupCount: 1,
|
|
TransparentGroupCount: 1,
|
|
TransparentDeferred: true,
|
|
OpaqueDigest: new RenderSceneHash128(11, 12),
|
|
TransparentDigest: new RenderSceneHash128(21, 22),
|
|
TransparentSetDigest: new RenderSceneHash128(31, 32),
|
|
Digest: new RenderSceneHash128(101, 202));
|
|
oracle.ObserveDispatcherSubmission(in submission);
|
|
CurrentRenderSceneOracleSnapshot first = oracle.Snapshot;
|
|
|
|
Assert.Equal(1uL, first.DispatcherFrameSequence);
|
|
Assert.Equal(1, first.DispatcherDrawCount);
|
|
Assert.Equal(1, first.DispatcherEntityCount);
|
|
Assert.Equal(1, first.DispatcherMeshRefCount);
|
|
Assert.Equal(3, first.DispatcherInstanceCount);
|
|
Assert.Equal(1, first.DispatcherOpaqueGroupCount);
|
|
Assert.Equal(1, first.DispatcherTransparentGroupCount);
|
|
CurrentRenderDispatcherFingerprint fingerprint =
|
|
Assert.Single(oracle.DispatcherCandidates);
|
|
Assert.Equal(LandblockB, fingerprint.TupleLandblockId);
|
|
Assert.Equal(LandblockA, fingerprint.CacheLandblockId);
|
|
|
|
oracle.BeginDispatcherFrame();
|
|
CurrentRenderSceneOracleSnapshot reset = oracle.Snapshot;
|
|
|
|
Assert.Equal(2uL, reset.DispatcherFrameSequence);
|
|
Assert.Equal(0, reset.DispatcherDrawCount);
|
|
Assert.Equal(0, reset.DispatcherMeshRefCount);
|
|
Assert.Empty(oracle.DispatcherCandidates);
|
|
Assert.NotEqual(first.DispatcherDigest, reset.DispatcherDigest);
|
|
}
|
|
|
|
[Fact]
|
|
public void DispatcherTemporaryBucket_DoesNotRewriteProjectionOwner()
|
|
{
|
|
WorldEntity outdoor = Entity(
|
|
id: 61,
|
|
serverGuid: 0,
|
|
parentCell: null);
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
var result = new InteriorEntityPartition.Result();
|
|
InteriorEntityPartition.Partition(
|
|
result,
|
|
[],
|
|
[Entry(LandblockA, outdoor)],
|
|
oracle);
|
|
|
|
oracle.BeginDispatcherFrame();
|
|
oracle.ObserveDispatcherDraw(
|
|
WbDrawDispatcher.EntitySet.All,
|
|
entitiesWalked: 1,
|
|
[(outdoor, 0, LandblockB)]);
|
|
|
|
CurrentRenderDispatcherFingerprint candidate =
|
|
Assert.Single(oracle.DispatcherCandidates);
|
|
Assert.Equal(LandblockA, candidate.Projection.LandblockId);
|
|
Assert.Equal(LandblockB, candidate.TupleLandblockId);
|
|
Assert.Equal(LandblockB, candidate.CacheLandblockId);
|
|
}
|
|
|
|
[Fact]
|
|
public void DispatcherAbortDiscardsIncompleteCandidates()
|
|
{
|
|
WorldEntity entity = Entity(
|
|
id: 70,
|
|
serverGuid: 0x8000_0070,
|
|
parentCell: CellA);
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
|
|
oracle.BeginDispatcherFrame();
|
|
oracle.ObserveDispatcherDraw(
|
|
WbDrawDispatcher.EntitySet.All,
|
|
entitiesWalked: 1,
|
|
[(entity, 0, LandblockA)]);
|
|
oracle.AbortDispatcherFrame();
|
|
|
|
Assert.Equal(1, oracle.Snapshot.AbortedDispatcherFrames);
|
|
Assert.Empty(oracle.DispatcherCandidates);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectionGeometryFingerprint_IsCachedAndFrameStorageIsReused()
|
|
{
|
|
const int partCount = 1_000;
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
var mesh = new RetailSelectionMesh(
|
|
Vector3.Zero,
|
|
2f,
|
|
[new RetailSelectionPolygon(
|
|
[
|
|
new(-1f, -1f, 0f),
|
|
new(1f, -1f, 0f),
|
|
new(1f, 1f, 0f),
|
|
new(-1f, 1f, 0f),
|
|
],
|
|
SingleSided: false)]);
|
|
// #250: one warm call is not enough to have paid the callee tree's
|
|
// first-call costs, and the frame body is a 1,000-iteration loop.
|
|
ZeroAllocationProbe.AssertAllocatesNothing(
|
|
"CurrentRenderSceneOracle selection frame publish",
|
|
PublishSelectionFrame);
|
|
|
|
Assert.Equal(partCount, oracle.Snapshot.SelectionPartCount);
|
|
|
|
void PublishSelectionFrame()
|
|
{
|
|
oracle.BeginSelectionFrame();
|
|
for (int index = 0; index < partCount; index++)
|
|
{
|
|
oracle.ObserveSelectionPart(
|
|
serverGuid: (uint)index + 1,
|
|
localEntityId: (uint)index + 1,
|
|
partIndex: index,
|
|
gfxObjId: 0x0100_0001u,
|
|
Matrix4x4.CreateTranslation(index, 0, 0),
|
|
mesh);
|
|
}
|
|
oracle.CompleteSelectionFrame();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SurfaceOverrideFingerprint_IsOrderIndependentAndContentExact()
|
|
{
|
|
var first = new Dictionary<uint, uint>
|
|
{
|
|
[0x0800_0002] = 0x0500_0012,
|
|
[0x0800_0001] = 0x0500_0011,
|
|
};
|
|
var reordered = new Dictionary<uint, uint>
|
|
{
|
|
[0x0800_0001] = 0x0500_0011,
|
|
[0x0800_0002] = 0x0500_0012,
|
|
};
|
|
var changed = new Dictionary<uint, uint>
|
|
{
|
|
[0x0800_0001] = 0x0500_0011,
|
|
[0x0800_0002] = 0x0500_0013,
|
|
};
|
|
|
|
RenderSceneHash128 expected =
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(first);
|
|
|
|
Assert.Equal(
|
|
expected,
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
|
|
reordered));
|
|
Assert.NotEqual(
|
|
expected,
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
|
|
changed));
|
|
Assert.NotEqual(
|
|
expected,
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(null));
|
|
}
|
|
|
|
[Fact]
|
|
public void SurfaceOverrideFingerprint_DictionaryHotPathAllocatesNothing()
|
|
{
|
|
var overrides = new Dictionary<uint, uint>
|
|
{
|
|
[0x0800_0001] = 0x0500_0011,
|
|
[0x0800_0002] = 0x0500_0012,
|
|
[0x0800_0003] = 0x0500_0013,
|
|
};
|
|
RenderSceneHash128 expected =
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
|
|
overrides);
|
|
RenderSceneHash128 actual = default;
|
|
|
|
// #250: the measured window was a 10,000-iteration loop written inline,
|
|
// which is exactly the shape on-stack replacement rewrites mid-flight,
|
|
// on this thread, inside the window.
|
|
ZeroAllocationProbe.AssertAllocatesNothing(
|
|
"CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint",
|
|
() => actual =
|
|
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
|
|
overrides));
|
|
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
|
|
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);
|
|
}
|