fix(world): rotate the DAT-scenery root too — the flyers actually orbit now
0c552eec wired the SetOmega hook and I called it done. The birds kept flapping
in place, and the user's report — "flapping and moving up and down, not
orbiting" — is what identified the miss: part animation working, root frozen.
BindLiveOwner THROWS on a zero ServerGuid, so owner.Body is only ever assigned
for server-spawned entities. Ambient flyers are DAT scenery with no ServerGuid
and therefore no PhysicsBody at all. The whole
if (owner.Body is { } body) { ... Frame::grotate ... }
block — and the omega application I added inside it — silently skipped every
object the fix was written for. It applied the mechanism to a branch these
objects never take.
So the omega now lives on the scheduler's own Owner record rather than on the
PhysicsBody, because most of this workset has no body, and the same grotate is
applied to entity.Rotation when there is none. That is not a shortcut around
the physics owner: for a DAT static the WorldEntity IS the only root retail
would be rotating.
Verified rather than assumed this time, both halves:
- StaticRenderProjectionJournal.SynchronizeActiveAnimatedSources re-projects
from the live entity every frame through
RenderTransform.FromRoot(entity.Position, entity.Rotation, entity.Scale),
so a rotated root reaches the renderer.
- Compose builds LOCAL part transforms, so the renderer composes root x part
and the offset mesh is carried around its circle.
Why it shipped broken: no test exercised a root rotation on the ServerGuid==0
branch, so applying omega body-only passed everything. The new test asserts the
rotation on the branch these objects actually take, and fails with the exact
production symptom (rotation stays identity) when the branch is disabled. Its
sibling pins the other direction — scenery without a SetOmega hook must never
acquire a spin.
Solution builds clean; 14,475 tests pass on the standard hermetic lane filter,
0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0c552eecac
commit
80a3a25594
2 changed files with 112 additions and 2 deletions
|
|
@ -32,6 +32,14 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
public required IReadOnlyDictionary<uint, uint>?[] SurfaceOverrides;
|
||||
public required bool[] PartAvailable;
|
||||
public PhysicsBody? Body;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CPhysicsObj::m_omegaVector</c>. It lives on the owner
|
||||
/// rather than on <see cref="Body"/> because DAT scenery — which is
|
||||
/// most of this workset, and every ambient flyer in it — never gets a
|
||||
/// PhysicsBody at all: <c>BindLiveOwner</c> refuses a zero ServerGuid.
|
||||
/// </summary>
|
||||
public Vector3 Omega;
|
||||
public AnimationSequencer? PendingProcessHooks;
|
||||
public ulong PendingResidencyVersion;
|
||||
public readonly List<PartTransform> PreparedLivePartFrames = new();
|
||||
|
|
@ -454,6 +462,19 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
}
|
||||
}
|
||||
|
||||
else if (owner.Omega != Vector3.Zero)
|
||||
{
|
||||
// Same grotate, on the only root these objects have. A DAT
|
||||
// static has no CPhysicsObj of its own, so the WorldEntity IS
|
||||
// the frame retail would be rotating — and the render
|
||||
// projection re-reads entity.Rotation every frame, so the
|
||||
// parts composed below orbit it.
|
||||
owner.RootFrameScratch.Origin = owner.Entity.Position;
|
||||
owner.RootFrameScratch.Orientation = owner.Entity.Rotation;
|
||||
FrameOps.GRotate(owner.RootFrameScratch, owner.Omega);
|
||||
owner.Entity.Rotation = owner.RootFrameScratch.Orientation;
|
||||
}
|
||||
|
||||
if (!IsResidentAtVersion(owner, residencyVersion))
|
||||
{
|
||||
InvalidatePending(owner);
|
||||
|
|
@ -535,9 +556,10 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
|
||||
private static void ApplyOmegaHooks(Owner owner, IReadOnlyList<AnimationHook> hooks)
|
||||
{
|
||||
if (owner.Body is not { } body)
|
||||
if (!TryResolveOmega(hooks, out Vector3 omega))
|
||||
return;
|
||||
if (TryResolveOmega(hooks, out Vector3 omega))
|
||||
owner.Omega = omega;
|
||||
if (owner.Body is { } body)
|
||||
body.Omega = omega;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue