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>
Ambient flyers played their wing animation and stayed put.
A Static object whose Setup declares a DefaultAnimation joins retail's
CPhysics::static_animating_objects workset (CPhysicsObj::InitDefaults
@0x00513A7B) and is driven by animate_static_object @0x00513DF0. That function
has exactly one motion step:
CPartArray::Update(part_array, dt, nullptr); // animate
Frame::grotate(&this->m_position.frame, &this->m_omegaVector);
Note the nullptr: unlike UpdatePositionInternal @0x00512C30, which combines the
animation's accumulated frame into the object's position, the static branch
DISCARDS it. These objects cannot move by animation translation at all. The
omega vector is the whole mechanism, and one thing writes it —
SetOmegaHook::Execute @0x00526F30 -> CPhysicsObj::set_omega @0x0050F6D0.
We decoded that hook and then dropped it on the floor: IAnimationHookSink's own
docs list SetOmegaHook among the unwired ones, and PhysicsBody.Omega was
assigned nowhere outside projectiles. The scheduler's GRotate call was already
correct — it was multiplying by a permanent zero.
The hook is now applied to the owning body at process_hooks time. Retail runs
process_hooks AFTER the grotate in the same pass, so a newly-set omega first
takes effect on the following frame; our Tick/ProcessHooks split already had
that order.
Scoped from the data rather than guessed. tools/AnimHookScan (new) walks the
dat: of 2,066 animations exactly 8 contain SetOmega, and all 8 are the
DefaultAnimation of one of the 8 setups that use it. No creature animation uses
it, so this belongs precisely where body.Omega is read and nowhere else.
The same scan is why the fix is believable as FLIGHT rather than a pirouette.
Every authored omega is pure yaw, and the setups' parts sit 5.6m, 4.2m, 12m and
36.8m from the origin they spin about. Rotating a frame whose mesh hangs 12m
off-axis carries it around a 12m circle — that offset IS the flight radius. An
installed-DAT test pins both properties, because the fix is only correct while
they hold and neither is visible from the code.
Also checked and deliberately NOT conflated: CSequence::set_omega @0x005248A0
writes CSequence::omega, a different field from CPhysicsObj::m_omegaVector,
fed by the motion table for creature turning. Only the latter drives grotate.
Solution builds clean; 14,473 tests pass on the standard hermetic lane filter
plus the new installed-DAT test, 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
C3c created graphical effect, projectile, and static-animation sidecars before Runtime finished the entity's first SetPosition. One-shot F754/F755 packets could be discarded, projectiles could adopt a cell-less body, and animated statics could compete for body ownership. Keep effects behind an exact-incarnation presentation barrier, retry projectile/static binding on the committed visibility edge, and keep effect cells synchronized with canonical rebuckets. User verified spell, recall, arrow, projectile, portal, and static presentation; 90 focused App tests and the Release build pass.
Maintain packed render worksets as lifecycle deltas arrive and synchronize active animated statics without scanning the resident world. This keeps Slice F non-drawing while preparing exact spatial and feature queries for shadow comparison.
Release: 8,206 passed, 5 skipped.
Own render, physics, DAT-static presentation, and retained static-resource rebinds behind retryable receipts so a failed publication resumes its exact unfinished suffix without replaying retail create-time defaults.
Co-authored-by: Codex <noreply@openai.com>
Own every borrowed PartArray frame across callbacks, version presentation state across appearance rebinding, reject recursive phase consumption, and preserve static MotionDone when only a stale visual pose is discarded.
Port CPartArray::UpdateParts minimum-count behavior: preserve the prior visual and rigid transforms for setup parts absent from a short authored AnimFrame, including the initial hydrated rest pose.
Co-Authored-By: OpenAI Codex <codex@openai.com>
Move final live part composition, exact-incarnation schedule handoff, MotionDone binding, and presentation diagnostics out of GameWindow while preserving the retail frame order. Reject stale schedule and completion ABA at the new owner boundary.
Co-Authored-By: OpenAI Codex <codex@openai.com>
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates.
Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean.
Co-authored-by: OpenAI Codex <codex@openai.com>