test(vfx): model the post-f24532ad effect cell and canonical body frame

f24532ad changed two presentation contracts that these fixtures still
expressed in their pre-change shape. Both failures date exactly to that
commit; they are independent of the world-frame family fixed in 6dcb94ac.

LiveEntityLightControllerTests.Refresh_FollowsCurrentTopLevelRootAndCell moved
the entity by writing ParentCellId alone. f24532ad now populates EffectCellId
at materialization and keeps it synchronized on canonical rebuckets
(LiveEntityRuntime.RebucketLiveEntity:845-856), because retail's
CPhysicsObj::set_cell changes the one CObjCell that ShouldDrawParticles reads.
EntityEffectPoseRegistry.UpdateRoot:163 resolves EffectCellId ?? ParentCellId,
so a production cell move writes both together and the old single-field move
left effects and lights on the stale materialization cell.

LiveEntityAnimationSchedulerTests.RetainedProjectileWithRemote_WhenMissileClears_TransfersMovementToRemoteOnce
seeded its shared remote body by assigning Position directly. Projectile
classification now validates and adopts the canonical body's own cell frame
(ProjectileController:184-190 - body.CellPosition.ObjCellId /
.Frame.Origin) rather than deriving it from the sidecar's FullCellId and the
streaming center, since a residence-managed Create legitimately still reports
FullCellId 0. A Runtime-committed body always carries its (cell, local) frame,
so the fixture now seeds it through the same SnapToCell placement API; leaving
it cell-less was correctly refused.

Both fixtures keep their original assertions - only the modelled world state
moved to match what production now commits.

Complete Release solution: 10,831 passed / 4 skipped / 0 failed
(App 4,048/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,247/1,
Headless 79, Runtime 1,009, UI 543).

Closes #281.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-03 13:32:46 +02:00
parent 6dcb94ac1b
commit 98e9f9e8c6
3 changed files with 51 additions and 1 deletions

View file

@ -305,8 +305,20 @@ public sealed class LiveEntityAnimationSchedulerTests
CellId = Cell,
Airborne = false,
};
remote.Body.Position = animation.Entity.Position;
remote.Body.Orientation = animation.Entity.Rotation;
// f24532ad: projectile classification now reads the canonical body's
// own cell frame (ProjectileController:184-190 —
// body.CellPosition.ObjCellId / .Frame.Origin) instead of deriving it
// from the sidecar's FullCellId and the streaming center, because a
// residence-managed Create legitimately still has FullCellId 0. A
// Runtime-committed body therefore always carries its (cell, local)
// frame; seed this shared body through the same placement API rather
// than assigning Position alone, which would leave it cell-less and
// be correctly refused.
remote.Body.SnapToCell(
Cell,
animation.Entity.Position,
animation.Entity.Position);
remote.Body.State = ProjectileState;
remote.Body.InWorld = true;
remote.Body.TransientState = TransientStateFlags.Active

View file

@ -22,7 +22,15 @@ public sealed class LiveEntityLightControllerTests
LightSource light = Assert.Single(fixture.Sink.GetOwnedLights(entity.Id)!);
entity.SetPosition(new Vector3(20, 30, 40));
// f24532ad: a canonical cell move now writes BOTH sidecar cells
// together (LiveEntityRuntime.RebucketLiveEntity:845-856), because
// retail's CPhysicsObj::set_cell changes the one CObjCell that
// ShouldDrawParticles reads. EntityEffectPoseRegistry.UpdateRoot:163
// resolves EffectCellId ?? ParentCellId, so moving the entity by
// ParentCellId alone would leave effects and lights on the stale
// materialization cell.
entity.ParentCellId = 0x01010002u;
entity.EffectCellId = 0x01010002u;
Assert.True(fixture.Poses.UpdateRoot(entity));
fixture.Controller.Refresh();