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:
parent
f30039d3d9
commit
c94a1a407e
20 changed files with 2141 additions and 1026 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue