feat(render): Campaign OVERHAUL S2 chunk 5 + closeout — registry is the only render membership owner

Chunk 5 (consumer cutover): WalkProductionWorldData's per-cell views are
borrowed from ShadowObjectRegistry.GetRetailPartEntriesInCell and resolved
through RenderSceneQuery.TryGetByLocalEntityId; every render-side sweep,
bucket, parent-cell and root-position fallback is deleted (AD-116 for the
one-frame registry→scene window, counted in UnregisteredRenderMembershipCount).
A live entity with visual parts but no collision geometry registers
render-only (LiveEntityCollisionBuilder computes the part array before the
empty-shapes gate).

Closeout fixes found while landing it:
- RefloodOwnerForLandblock forwards the retained part array — a reflood is
  retail's recalc_cross_cells over the SAME CPartArray; without it every owner
  touched by a landblock replacement commit lost its render membership.
- Non-colliding DAT statics register render-only from BOTH publishers
  (LandblockPhysicsPublisher.PublishStaticEntity,
  LandblockPhysicsContentBuilder.RegisterRenderOnlyStatic). The G2 self-gate
  pixel diff caught them vanishing (Facility Hub wall panels): retail floods
  every object regardless of collision (CEnvCell::init_static_objects
  0x0052c350, add_shadows_to_cells 0x00514ae0).
- S2 dual review fix batch (arch + retail lens, lead-verified):
  Suspend clears the retail product (remove_shadows_from_cells 0x00511230 is
  one transaction); AttachChild/DetachChild advance the mutation revision so
  a prepared SetPosition cannot clobber a child's rows; an attached child
  never floods on its own re-registration; RemoveLandblock and the non-rooted
  RetireOwnerFromLandblock prune retail rows (render-only statics end with
  their landblock); a render-only owner's no-cell-array commit republishes at
  its destination cell (AD-117); an empty non-null part array is treated as
  null; per-move closures/LINQ replaced by index loops; EnvCell shells stay
  out of the scene's LocalEntityId index (payload-less records); the index
  predicate compares the id; the dead per-cell scene indices are deleted.

Register: AD-116 (chunk 5), AD-117 (four residual Contract A/B readings).
Evidence: s2-membership-ownership-map.md §8 (chunk 5) and §9 (closeout).

Gates (Release): Core 4,984/4,984; Content 214/214; Runtime 1,884/1,884;
App hermetic lane 6,760/6,760; App InstalledDat lane 217 pass / 1 skip /
2 pre-existing #383 layout-fixture failures; App Windows lane 1/1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-03 01:01:47 +02:00
parent f30039d3d9
commit c94a1a407e
20 changed files with 2141 additions and 1026 deletions

View file

@ -520,13 +520,6 @@ public sealed class ArchRenderSceneTests
LightCandidate: 1,
Dirty: 6),
query.IndexCounts);
Assert.Equal(1, query.GetCellStaticCount(indoorCell));
Assert.Equal(1, query.GetCellDynamicCount(indoorCell));
var cellRecords = new RenderProjectionRecord[1];
Assert.Equal(1, query.CopyCellStaticsTo(indoorCell, cellRecords));
Assert.Equal(indoorStatic.Id, cellRecords[0].Id);
Assert.Equal(1, query.CopyCellDynamicsTo(indoorCell, cellRecords));
Assert.Equal(cellDynamic.Id, cellRecords[0].Id);
Assert.True(scene.Memory.EstimatedIndexBytes > 0);
}
@ -580,7 +573,6 @@ public sealed class ArchRenderSceneTests
Assert.Equal(1, updated.IndexCounts.Translucent);
Assert.Equal(1, updated.IndexCounts.LightCandidate);
Assert.Equal(1, updated.IndexCounts.Dirty);
Assert.Equal(1, updated.GetCellDynamicCount(indoorCell));
RenderProjectionRecord replacement = rebucketed with
{
@ -592,7 +584,6 @@ public sealed class ArchRenderSceneTests
RenderSceneQuery replaced = scene.OpenQuery();
Assert.Equal(0, replaced.IndexCounts.Dynamic);
Assert.Equal(1, replaced.IndexCounts.IndoorCellStatic);
Assert.Equal(1, replaced.GetCellStaticCount(indoorCell));
scene.Apply(
[
@ -1085,6 +1076,92 @@ public sealed class ArchRenderSceneTests
Assert.Equal(changed.LatestRevision, query.DirectionalShadowTransformRevision);
}
// Campaign OVERHAUL S2 review fix round (F5/F6): the LocalEntityId index.
private static RenderProjectionRecord WithLocalEntityId(
RenderProjectionRecord record,
uint localEntityId) =>
record with
{
Source = new RenderSourceMetadata(
LocalEntityId: localEntityId,
ServerGuid: 0,
SourceId: 1,
ParentCellId: record.Source.ParentCellId,
EffectCellId: 0,
BuildingShellAnchorCellId: 0,
TransformFingerprint: new RenderSceneHash128(1, 1),
GeometryFingerprint: new RenderSceneHash128(2, 2),
AppearanceFingerprint: new RenderSceneHash128(3, 3)),
};
[Fact]
public void TryGetByLocalEntityId_IgnoresEnvCellShellRecords()
{
// An EnvCell shell carries its CELL id in the entity-id slot; a
// dungeon cell id such as 0x8A020100 is numerically a procedural
// scenery entity id. The shell must never shadow the real entity.
const uint aliasedId = 0x8A020100u;
RenderSceneGeneration generation = Generation(31);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord shell = WithLocalEntityId(
Record(310, 1, RenderProjectionClass.IndoorCellStatic), aliasedId);
RenderProjectionRecord scenery = WithLocalEntityId(
Record(311, 1, RenderProjectionClass.OutdoorStatic), aliasedId);
scene.Apply(
[
RenderProjectionDelta.Register(generation, 1, shell),
RenderProjectionDelta.Register(generation, 2, scenery),
]);
RenderSceneQuery query = scene.OpenQuery();
Assert.True(query.TryGetByLocalEntityId(aliasedId, out RenderProjectionRecord found));
Assert.Equal(scenery.Id, found.Id);
// Unregistering the shell never disturbs the entity's mapping.
scene.Apply(
[
RenderProjectionDelta.Unregister(
generation,
3,
shell.Id,
shell.OwnerIncarnation),
]);
query = scene.OpenQuery();
Assert.True(query.TryGetByLocalEntityId(aliasedId, out found));
Assert.Equal(scenery.Id, found.Id);
}
[Fact]
public void Update_ThatRebindsTheLocalEntityId_MovesTheIndex()
{
RenderSceneGeneration generation = Generation(32);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = WithLocalEntityId(
Record(320, 1, RenderProjectionClass.LiveDynamicRoot), 19);
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
Assert.True(scene.OpenQuery().TryGetByLocalEntityId(19, out _));
// Same class, cell, flags, mesh count and sort key — only the id
// changes; the index must follow it (IndexMembershipEquals compares
// the id since the S2 review fix).
RenderProjectionRecord rebound = WithLocalEntityId(original, 20);
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
2,
rebound),
]);
RenderSceneQuery query = scene.OpenQuery();
Assert.False(query.TryGetByLocalEntityId(19, out _));
Assert.True(query.TryGetByLocalEntityId(20, out RenderProjectionRecord found));
Assert.Equal(original.Id, found.Id);
}
private static RenderProjectionRecord Record(
ulong id,
ulong incarnation,

View file

@ -103,7 +103,6 @@ public sealed class DirectionalShadowCasterFrameTests
Assert.Equal(
[RenderSceneIndex.OutdoorStatic, RenderSceneIndex.OutdoorDynamic],
source.CopiedIndices);
Assert.Equal(0, source.CellQueries);
Assert.Equal(2, frame.Stats.IndexCopies);
}
@ -635,7 +634,6 @@ public sealed class DirectionalShadowCasterFrameTests
public int IndexCountReads { get; private set; }
public int IndexCopies { get; private set; }
public int CellQueries { get; private set; }
public int ProjectionReads { get; private set; }
public int BatchedProjectionCopies { get; private set; }
public ulong TopologyRevision { get; private set; } = 1;
@ -731,6 +729,32 @@ public sealed class DirectionalShadowCasterFrameTests
return false;
}
public bool TryGetByLocalEntityId(
RenderSceneGeneration generation,
uint localEntityId,
out RenderProjectionRecord record)
{
for (int index = 0; index < _statics.Length; index++)
{
if (_statics[index].Source.LocalEntityId == localEntityId)
{
record = _statics[index];
return true;
}
}
for (int index = 0; index < _dynamics.Length; index++)
{
if (_dynamics[index].Source.LocalEntityId == localEntityId)
{
record = _dynamics[index];
return true;
}
}
record = default;
return false;
}
public int CopyById(
RenderSceneGeneration generation,
ReadOnlySpan<RenderProjectionId> ids,
@ -791,25 +815,6 @@ public sealed class DirectionalShadowCasterFrameTests
return values.Length;
}
public int GetCellCount(
RenderSceneGeneration generation,
uint fullCellId,
bool dynamic)
{
CellQueries++;
throw new InvalidOperationException("Directional shadows do not query PView cells.");
}
public int CopyCellTo(
RenderSceneGeneration generation,
uint fullCellId,
bool dynamic,
Span<RenderProjectionRecord> destination)
{
CellQueries++;
throw new InvalidOperationException("Directional shadows do not query PView cells.");
}
public void ReplaceStatics(
RenderProjectionRecord[] values,

View file

@ -316,7 +316,7 @@ public class Issue177StairDescentCameraFloodTests
Assert.Contains(
worldData.GetCellStatics(FacilityHub | 0x015Eu).Records,
record => record.Id == projectionId);
Assert.Equal(0, worldData.UnregisteredStaticRenderFallbackCount);
Assert.Equal(0, worldData.UnregisteredRenderMembershipCount);
}
/// <summary>

View file

@ -3,9 +3,23 @@ using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
using AcDream.App.Rendering.Walk;
using AcDream.Core.Physics;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>
/// Campaign OVERHAUL S2 chunk 5: <see cref="WalkProductionWorldData"/> no
/// longer sweeps the scene to rebuild membership into bucket dictionaries —
/// <c>GetCellStatics</c>/<c>GetCellDynamics</c>/<c>GetOutdoorStatics</c>/
/// <c>GetOutdoorDynamics</c> are borrowed, on-demand views over
/// <see cref="ShadowObjectRegistry"/>'s retail per-cell part-entry product,
/// resolved back to a <see cref="RenderProjectionRecord"/> through
/// <see cref="RenderSceneQuery.TryGetByLocalEntityId"/>. Every test below
/// drives the REAL production path end to end — a real (bare, no-DAT)
/// <see cref="ShadowObjectRegistry"/> and a real <see cref="ArchRenderScene"/>
/// — rather than a hand-fed bucket dictionary, since the resolution itself IS
/// the thing under test now.
/// </summary>
public sealed class WalkProductionWorldDataTests
{
[Fact]
@ -79,288 +93,6 @@ public sealed class WalkProductionWorldDataTests
WalkProductionWorldData.BuildingShellBucketCellId(building));
}
[Fact]
public void BucketOutdoorRecord_UsesEveryOutdoorPhysicsShadowCellAcrossLandblockEdge()
{
RenderProjectionRecord record = Record(
id: 0x1234u,
position: new Vector3(191f, 191f, 0f));
var buckets = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketOutdoorRecord(
in record,
[0xF07F0040u, 0xF0800001u, 0xF4180101u],
buckets,
renderCenterLbX: 0xF0,
renderCenterLbY: 0x7F);
Assert.Equal([0xF07F0040u, 0xF0800001u], buckets.Keys.Order());
Assert.All(buckets.Values, bucket => Assert.Equal(record, Assert.Single(bucket)));
}
[Fact]
public void BucketOutdoorRecord_UnregisteredEffectFallsBackToRootPositionCell()
{
RenderProjectionRecord record = Record(
id: 0x5678u,
position: new Vector3(193f, 25f, 0f));
var buckets = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketOutdoorRecord(
in record,
Array.Empty<uint>(),
buckets,
renderCenterLbX: 0xEF,
renderCenterLbY: 0x7F);
Assert.Equal([0xF07F0002u], buckets.Keys);
Assert.Equal(record, Assert.Single(buckets[0xF07F0002u]));
}
[Fact]
public void BucketIndoorRecord_InstallsCrossCellPartInBothInteriorCells()
{
RenderProjectionRecord record = Record(
id: 0x48A02035u,
position: new Vector3(55.25f, -46.47f, -3f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x48A02035u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketIndoorRecord(
in record,
[0x8A02015Fu, 0x8A02015Eu],
indoor,
outdoor);
Assert.Equal([0x8A02015Eu, 0x8A02015Fu], indoor.Keys.Order());
Assert.All(indoor.Values, bucket => Assert.Equal(record, Assert.Single(bucket)));
Assert.Empty(outdoor);
}
[Fact]
public void BucketIndoorRecord_CanCrossAnExitIntoLandscapeCell()
{
RenderProjectionRecord record = Record(
id: 0x48A02035u,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x48A02035u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketIndoorRecord(
in record,
[0x8A02015Fu, 0x8A020021u],
indoor,
outdoor);
Assert.Equal(record, Assert.Single(indoor[0x8A02015Fu]));
Assert.Equal(record, Assert.Single(outdoor[0x8A020021u]));
}
[Fact]
public void BucketDynamicRecord_InstallsMultipartPlayerInEveryCrossedInteriorCell()
{
RenderProjectionRecord record = Record(
id: 0x000F4243u,
position: new Vector3(58.81f, -49.42f, -0.85f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x000F4243u,
ParentCellId = 0x8A02015Eu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketDynamicRecord(
in record,
[0x8A02015Eu, 0x8A02015Fu, 0x8A0201C1u],
indoor,
outdoor,
renderCenterLbX: 0x8A,
renderCenterLbY: 0x02);
Assert.Equal(
[0x8A02015Eu, 0x8A02015Fu, 0x8A0201C1u],
indoor.Keys.Order());
Assert.All(
indoor.Values,
bucket => Assert.Equal(record, Assert.Single(bucket)));
Assert.Empty(outdoor);
}
[Fact]
public void BucketDynamicRecord_UnregisteredInteriorEffectFallsBackToParentCell()
{
RenderProjectionRecord record = Record(
id: 0x00001234u,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x00001234u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketDynamicRecord(
in record,
Array.Empty<uint>(),
indoor,
outdoor,
renderCenterLbX: 0x8A,
renderCenterLbY: 0x02);
Assert.Equal(record, Assert.Single(indoor[0x8A02015Fu]));
Assert.Empty(outdoor);
}
// Campaign OVERHAUL S2 chunk 4: ResolveDynamicRenderCells is now a direct
// TryGetRetailCellArray read for EVERY dynamic record, children included
// — ShadowObjectRegistry.AttachChild owns Contract B's child-inheritance
// recursion at the registry, so the render-side parent-chain walk this
// test used to exercise no longer exists.
private static ShadowShape ChildBsp(uint gfxObjId) =>
ShadowShape.Bsp(
gfxObjId,
Vector3.Zero,
Quaternion.Identity,
scale: 1f,
localGeometry: ShadowPartGeometry.Create(
new FlatCollisionSphere(Vector3.Zero, 1f), null));
[Fact]
public void ResolveDynamicRenderCells_AttachedChildReadsTheRegistrysRetailCellArrayDirectly()
{
var shadows = new ShadowObjectRegistry();
const uint rootId = 0x5000000Au;
const uint childId = 0x800045EEu;
IReadOnlyList<ShadowShape> rootParts = new[] { ChildBsp(0x02000001u) };
shadows.RegisterMultiPart(
rootId,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
rootParts,
state: 0u,
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0xF4180000u,
seedCellId: 0xF4180104u,
isStatic: false,
partArray: rootParts);
Assert.True(shadows.TryGetRetailCellArray(rootId, out IReadOnlyList<uint> rootCells));
Assert.NotEmpty(rootCells); // the fixture must actually exercise a flood
// No render-side parent walk any more: Contract B's inheritance is
// owned by the registry at attach time.
Assert.True(shadows.AttachChild(childId, rootId, new[] { ChildBsp(0x02000002u) }));
RenderProjectionRecord wand = Record(
id: childId,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = childId,
ParentCellId = 0xF4180104u,
},
EntityPayload = new RenderEntityPayload() with
{
CasterIdentity = RenderCasterIdentityKind.EquippedChild,
},
};
IReadOnlyList<uint> cells =
WalkProductionWorldData.ResolveDynamicRenderCells(
in wand,
id => shadows.TryGetRetailCellArray(id, out IReadOnlyList<uint> c)
? (true, c)
: (false, Array.Empty<uint>()),
shadows.GetOwnerCells,
out bool usedFallback);
Assert.False(usedFallback);
Assert.Equal(rootCells, cells);
}
[Fact]
public void ResolveDynamicRenderCells_UnregisteredEntityFallsBackToOwnerCells()
{
var shadows = new ShadowObjectRegistry();
const uint entityId = 0x00099Fu;
IReadOnlyList<ShadowShape> shapes = new[] { ChildBsp(0x02000003u) };
shadows.RegisterMultiPart(
entityId,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
shapes,
state: 0u,
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0xF4180000u,
seedCellId: 0xF4180104u,
isStatic: false);
// No partArray supplied above -> no retail cell array registered for
// this entity (item E of the S2 chunk-1 contract), so the ordinary
// collision-flood answer is the conservative fallback.
Assert.False(shadows.TryGetRetailCellArray(entityId, out _));
Assert.NotEmpty(shadows.GetOwnerCells(entityId));
RenderProjectionRecord record = Record(
id: entityId,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = entityId,
ParentCellId = 0xF4180104u,
},
};
IReadOnlyList<uint> cells =
WalkProductionWorldData.ResolveDynamicRenderCells(
in record,
id => shadows.TryGetRetailCellArray(id, out IReadOnlyList<uint> c)
? (true, c)
: (false, Array.Empty<uint>()),
shadows.GetOwnerCells,
out bool usedFallback);
Assert.True(usedFallback);
Assert.Equal(shadows.GetOwnerCells(entityId), cells);
}
// -----------------------------------------------------------------
// Campaign OVERHAUL S2 chunk 2: BeginFrame's indoor static sweep now
// borrows membership from ShadowObjectRegistry.TryGetRetailCellArray
// instead of rebuilding it (the deleted ResolveStaticRenderCells /
// ShadowObjectRegistry.ComputeStaticRenderCells pair). These two tests
// drive the REAL production path end to end — a real (bare, no-DAT)
// ShadowObjectRegistry, a real ArchRenderScene, and BeginFrame itself —
// rather than the hardcoded-cell-array style of the Bucket* tests above,
// which exercise only the (unchanged) bucketing primitive.
// -----------------------------------------------------------------
private static ShadowShape Bsp(uint gfxObjId, float radius = 1f) =>
ShadowShape.Bsp(
gfxObjId,
@ -388,20 +120,54 @@ public sealed class WalkProductionWorldDataTests
SourceId = sourceId,
ParentCellId = parentCellId,
},
// Every production entity record carries its MeshRefs
// (RenderProjectionRecordFactory); only an EnvCell SHELL has no
// payload, and ArchRenderScene keeps shells out of the
// LocalEntityId index on exactly that distinction.
EntityPayload = new RenderEntityPayload() with
{
MeshRefs = [new MeshRef(sourceId, Matrix4x4.Identity)],
},
};
[Fact]
public void BeginFrame_IndoorStatic_RegisteredEntityUsesRegistryRetailCellArray()
{
const uint entityId = 0x48A02001u;
const uint retailCellId = 0x8A02015Fu;
// A DECOY parent cell, deliberately different from the registered
// retail array's cell, so the assertions below can only pass if the
// indoor sweep actually consulted the registry rather than falling
// back to the authored parent.
const uint decoyParentCellId = 0x8A0201C1u;
private static RenderProjectionRecord DynamicRecord(
uint entityId, uint sourceId, uint parentCellId) =>
IndoorStaticRecord(entityId, sourceId, parentCellId) with
{
Id = RenderProjectionId.FromRaw(0x0100_0000_0000_0000u | entityId),
ProjectionClass = RenderProjectionClass.LiveDynamicRoot,
};
var shadows = new ShadowObjectRegistry();
private static RenderProjectionRecord OutdoorStaticRecord(
uint entityId, uint sourceId, uint cellId, bool isBuildingShell = false) =>
new RenderProjectionRecord() with
{
Id = RenderProjectionId.FromRaw(0x0200_0000_0000_0000u | entityId),
ProjectionClass = RenderProjectionClass.OutdoorStatic,
OwnerIncarnation = RenderOwnerIncarnation.FromRaw(1),
Transform = new RenderTransform(Matrix4x4.Identity),
PreviousTransform = new PreviousRenderTransform(Matrix4x4.Identity),
Residency = new RenderSpatialResidency(
RenderSpatialBucket.FromRaw(cellId), cellId & 0xFFFF0000u, cellId),
Flags = RenderProjectionFlags.Draw,
Source = new RenderSourceMetadata() with
{
LocalEntityId = entityId,
SourceId = sourceId,
ParentCellId = cellId,
},
EntityPayload = new RenderEntityPayload() with
{
IsBuildingShell = isBuildingShell,
},
};
private static void Register(
ShadowObjectRegistry shadows,
uint entityId,
uint seedCellId,
uint landblockId)
{
IReadOnlyList<ShadowShape> parts = new[] { Bsp(0x01001234u) };
shadows.RegisterMultiPart(
entityId,
@ -412,10 +178,26 @@ public sealed class WalkProductionWorldDataTests
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0x8A020000u,
seedCellId: retailCellId,
landblockId: landblockId,
seedCellId: seedCellId,
isStatic: true,
partArray: parts);
}
[Fact]
public void GetCellStatics_RegisteredEntityIsResolvedByLocalEntityIdAtTheRetailCell()
{
const uint entityId = 0x48A02001u;
const uint retailCellId = 0x8A02015Fu;
// A DECOY parent cell, deliberately different from the registered
// retail array's cell, so the assertions below can only pass if the
// per-cell view actually consulted the registry rather than the
// authored parent (deleted alongside the sweep this test used to
// exercise).
const uint decoyParentCellId = 0x8A0201C1u;
var shadows = new ShadowObjectRegistry();
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> retailCells));
Assert.Equal(new[] { retailCellId }, retailCells);
@ -435,34 +217,265 @@ public sealed class WalkProductionWorldDataTests
Assert.DoesNotContain(
worldData.GetCellStatics(decoyParentCellId).Records,
record => record.Id == projection.Id);
Assert.Equal(0, worldData.UnregisteredStaticRenderFallbackCount);
Assert.Equal(0, worldData.UnregisteredRenderMembershipCount);
}
[Fact]
public void BeginFrame_IndoorStatic_UnregisteredEntityFallsBackToParentCellAndCountsFallback()
public void GetCellStatics_RegistryAheadOfSceneContributesToNoCellAndCountsFallback()
{
const uint entityId = 0x48A02002u;
const uint retailCellId = 0x8A02015Fu;
// The registry HAS flooded this entity into its retail CELLARRAY —
// exactly the streaming-window race chunk 5 keeps as the ONE
// remaining fallback: the physics publisher (registry) ran before
// the presentation journal applied this frame's projected record.
var shadows = new ShadowObjectRegistry();
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
Assert.True(shadows.TryGetRetailCellArray(entityId, out _));
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
// Deliberately no scene.Apply — the projected record does not exist
// yet this frame.
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.Equal(0, worldData.GetCellStatics(retailCellId).Records.Count);
Assert.Equal(1, worldData.UnregisteredRenderMembershipCount);
}
[Fact]
public void GetCellStatics_UnregisteredEntityContributesToNoCellWithoutCounting()
{
// The OPPOSITE race: the scene has a projected record, but the
// registry never registered a retail CELLARRAY for this entity at
// all. Under the borrowed-view model this entity is never visited
// (nothing in the registry names it), so it correctly appears in NO
// cell and does not inflate the one remaining fallback counter —
// that counter only tracks entities the REGISTRY has flooded.
const uint entityId = 0x48A02003u;
const uint parentCellId = 0x8A02015Fu;
// Bare, empty registry: entityId is never registered, so
// TryGetRetailCellArray must answer false for it.
var shadows = new ShadowObjectRegistry();
Assert.False(shadows.TryGetRetailCellArray(entityId, out _));
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
IndoorStaticRecord(entityId, sourceId: 0x02000002u, parentCellId);
IndoorStaticRecord(entityId, sourceId: 0x02000003u, parentCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.Equal(0, worldData.GetCellStatics(parentCellId).Records.Count);
Assert.Equal(0, worldData.UnregisteredRenderMembershipCount);
}
[Fact]
public void UnregisteredRenderMembershipCount_CountsDistinctEntitiesNotCellVisits()
{
// One entity crossing SEVERAL cells — outdoor, since the bbox flood's
// fixed outdoor expansion reliably crosses cells even against a bare
// registry with no portal DAT data to cross INDOOR cells with (see
// GetOutdoorStatics_UsesTheSameRegistryDrivenViewAsIndoor) — all
// unresolved in the scene, must count once total, not once per cell
// the registry flooded it into.
const uint entityId = 0x87640002u;
const uint seedCellId = 0x87640030u;
IReadOnlyList<ShadowShape> parts = new[] { Bsp(0x01001234u) };
var shadows = new ShadowObjectRegistry();
shadows.RegisterMultiPart(
entityId,
Vector3.Zero,
Quaternion.Identity,
parts,
state: 0u,
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0x87640000u,
seedCellId: seedCellId,
isStatic: true,
partArray: parts);
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> cells));
Assert.True(cells.Count >= 2, "fixture must actually cross more than one cell");
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x87640000u, renderCenterLbX: 0x87, renderCenterLbY: 0x64);
foreach (uint cellId in cells)
Assert.Equal(0, worldData.GetOutdoorStatics(cellId).Records.Count);
Assert.Equal(1, worldData.UnregisteredRenderMembershipCount);
}
[Fact]
public void GetCellDynamics_OnlyReturnsDynamicClassRecordsFromTheSameCell()
{
const uint staticEntityId = 0x48A02010u;
const uint dynamicEntityId = 0x48A02011u;
const uint sharedCellId = 0x8A02015Fu;
var shadows = new ShadowObjectRegistry();
Register(shadows, staticEntityId, sharedCellId, landblockId: 0x8A020000u);
Register(shadows, dynamicEntityId, sharedCellId, landblockId: 0x8A020000u);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord staticProjection =
IndoorStaticRecord(staticEntityId, sourceId: 0x02000010u, sharedCellId);
RenderProjectionRecord dynamicProjection =
DynamicRecord(dynamicEntityId, sourceId: 0x02000011u, sharedCellId);
scene.Apply([
RenderProjectionDelta.Register(generation, 1, staticProjection),
RenderProjectionDelta.Register(generation, 2, dynamicProjection),
]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.Contains(
worldData.GetCellStatics(parentCellId).Records,
worldData.GetCellStatics(sharedCellId).Records,
record => record.Id == staticProjection.Id);
Assert.DoesNotContain(
worldData.GetCellStatics(sharedCellId).Records,
record => record.Id == dynamicProjection.Id);
Assert.Contains(
worldData.GetCellDynamics(sharedCellId).Records,
record => record.Id == dynamicProjection.Id);
Assert.DoesNotContain(
worldData.GetCellDynamics(sharedCellId).Records,
record => record.Id == staticProjection.Id);
}
[Fact]
public void GetCellStatics_ExcludesBuildingShellRecordsFromTheSameCell()
{
const uint shellEntityId = 0x48A02020u;
const uint ordinaryEntityId = 0x48A02021u;
const uint sharedCellId = 0x8A02015Fu;
var shadows = new ShadowObjectRegistry();
Register(shadows, shellEntityId, sharedCellId, landblockId: 0x8A020000u);
Register(shadows, ordinaryEntityId, sharedCellId, landblockId: 0x8A020000u);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord shellProjection =
IndoorStaticRecord(shellEntityId, sourceId: 0x02000020u, sharedCellId) with
{
EntityPayload = new RenderEntityPayload() with { IsBuildingShell = true },
};
RenderProjectionRecord ordinaryProjection =
IndoorStaticRecord(ordinaryEntityId, sourceId: 0x02000021u, sharedCellId);
scene.Apply([
RenderProjectionDelta.Register(generation, 1, shellProjection),
RenderProjectionDelta.Register(generation, 2, ordinaryProjection),
]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.DoesNotContain(
worldData.GetCellStatics(sharedCellId).Records,
record => record.Id == shellProjection.Id);
Assert.Contains(
worldData.GetCellStatics(sharedCellId).Records,
record => record.Id == ordinaryProjection.Id);
}
[Fact]
public void GetOutdoorStatics_UsesTheSameRegistryDrivenViewAsIndoor()
{
const uint entityId = 0x87640001u;
const uint outdoorCellId = 0x87640030u;
var shadows = new ShadowObjectRegistry();
Register(shadows, entityId, outdoorCellId, landblockId: 0x87640000u);
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> retailCells));
// The outdoor bbox flood's fixed expansion crosses more than the
// seed cell even for a small radius; only the seed cell's own
// membership matters for this test.
Assert.Contains(outdoorCellId, retailCells);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
OutdoorStaticRecord(entityId, sourceId: 0x02000030u, outdoorCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x87640000u, renderCenterLbX: 0x87, renderCenterLbY: 0x64);
Assert.Contains(
worldData.GetOutdoorStatics(outdoorCellId).Records,
record => record.Id == projection.Id);
Assert.Equal(1, worldData.UnregisteredStaticRenderFallbackCount);
}
[Fact]
public void GetCellStatics_CachesTheResultForTheRestOfTheFrame()
{
const uint entityId = 0x48A02030u;
const uint retailCellId = 0x8A02015Fu;
var shadows = new ShadowObjectRegistry();
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
IndoorStaticRecord(entityId, sourceId: 0x02000030u, retailCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
WalkFrameStaticRecords first = worldData.GetCellStatics(retailCellId);
WalkFrameStaticRecords second = worldData.GetCellStatics(retailCellId);
Assert.Equal(first.Records.Array, second.Records.Array);
Assert.Equal(first.Records.Offset, second.Records.Offset);
Assert.Equal(first.Records.Count, second.Records.Count);
}
[Fact]
public void StaticBucketContains_FindsARegisteredSourceIdInItsRetailCell()
{
const uint entityId = 0x48A02040u;
const uint sourceId = 0x020009A2u;
const uint retailCellId = 0xF4180112u;
var shadows = new ShadowObjectRegistry();
Register(shadows, entityId, retailCellId, landblockId: 0xF4180000u);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
IndoorStaticRecord(entityId, sourceId, retailCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0xF4180000u, renderCenterLbX: 0xF4, renderCenterLbY: 0x18);
Assert.True(worldData.StaticBucketContains(retailCellId, sourceId));
Assert.False(worldData.StaticBucketContains(retailCellId, sourceId + 1));
Assert.False(worldData.StaticBucketContains(0xF4180107u, sourceId));
}
private static RenderProjectionRecord Record(uint id, Vector3 position) =>