feat(physics): S2 chunk 4 - movement publishes from the transition's cells; children inherit at registration
Movement: CommitSetPosition's RefreshPositionRows/ReplacePositionRows and the staged apply publish the retail render product from the exact cell list collision just used, the transition's cell_array retail feeds add_shadows_to_cells in CPhysicsObj::SetPositionInternal @0x00515330 (pseudo-C 283526-283539); the separate move-path bbox recompute is deleted. calc_cross_cells @0x00515230 stays the distinct full-recompute path (PhysicsShadowCommitAction.Recalculate). Children (Contract B recursion): ShadowObjectRegistry.AttachChild/DetachChild give an attached object the root's current cells as part entries only, republished whenever the root's array changes, detached at withdrawal and cascaded from the root's Deregister; nested attachment resolves to the root with a bounded, cycle-safe chain. EquippedChildRenderController attaches at realization (FromSetupRenderParts over the child's Setup) and detaches at its single removal funnel. WalkProductionWorldData's dynamic sweep reads TryGetRetailCellArray directly; the 64-hop parent-chain walk and its FindParentLocalId plumbing are deleted. CollisionWorldState.Clear now also clears the retail products. Gates (implementer's isolated worktree at identical content): Release build 0/0; Core 4,970/4,970; App hermetic 6,761/6,761; targeted walk/child/live-entity/placement/comparator 166/166; Runtime 1,884/1,884. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
706fc49397
commit
75ea269d35
14 changed files with 1072 additions and 179 deletions
|
|
@ -231,16 +231,56 @@ public sealed class WalkProductionWorldDataTests
|
|||
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_EquippedDescendantInheritsRootCellArray()
|
||||
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: 0x800045EEu,
|
||||
id: childId,
|
||||
position: Vector3.Zero) with
|
||||
{
|
||||
Source = new RenderSourceMetadata() with
|
||||
{
|
||||
LocalEntityId = 0x800045EEu,
|
||||
LocalEntityId = childId,
|
||||
ParentCellId = 0xF4180104u,
|
||||
},
|
||||
EntityPayload = new RenderEntityPayload() with
|
||||
|
|
@ -248,27 +288,66 @@ public sealed class WalkProductionWorldDataTests
|
|||
CasterIdentity = RenderCasterIdentityKind.EquippedChild,
|
||||
},
|
||||
};
|
||||
var owners = new Dictionary<uint, IReadOnlyList<uint>>
|
||||
{
|
||||
[0x5000000Au] = [0xF4180106u, 0xF4180104u],
|
||||
};
|
||||
var parents = new Dictionary<uint, uint>
|
||||
{
|
||||
[0x800045EEu] = 0x80000461u,
|
||||
[0x80000461u] = 0x5000000Au,
|
||||
};
|
||||
|
||||
IReadOnlyList<uint> cells =
|
||||
WalkProductionWorldData.ResolveDynamicRenderCells(
|
||||
in wand,
|
||||
id => owners.TryGetValue(id, out IReadOnlyList<uint>? value)
|
||||
? value
|
||||
: Array.Empty<uint>(),
|
||||
id => parents.TryGetValue(id, out uint value)
|
||||
? value
|
||||
: null);
|
||||
id => shadows.TryGetRetailCellArray(id, out IReadOnlyList<uint> c)
|
||||
? (true, c)
|
||||
: (false, Array.Empty<uint>()),
|
||||
shadows.GetOwnerCells,
|
||||
out bool usedFallback);
|
||||
|
||||
Assert.Equal([0xF4180106u, 0xF4180104u], cells);
|
||||
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);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue