feat(physics): integrate live projectile runtime

Attach the retail projectile driver to canonical LiveEntityRecord ownership, sharing one PhysicsBody and full-cell identity with RemoteMotion regardless of creation order. Apply timestamp-gated state, vector, and position corrections in place, preserve active ordinary-body behavior when Missile clears, and keep renderer, effects, shadows, and spatial buckets synchronized across loaded/pending transitions.

Validate malformed packets before canonical timestamp mutation, validate adopted bodies from their current frame rather than stale CreateObject data, serialize late Setup resolution with streaming DAT reads, and preserve classification across clock anomalies. Retain shadow registrations through temporary leave-world residence while logical teardown remains generation-scoped.

Add 31 App lifecycle/adversarial tests plus Core shadow suspension coverage, and synchronize the retail pseudocode, architecture, milestones, roadmap, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-14 12:51:42 +02:00
parent f02b995e4f
commit a51ebc66e9
12 changed files with 2559 additions and 32 deletions

View file

@ -872,3 +872,105 @@ observable ordering without exposing a half-committed pose to modern sinks.
Boundary conformance covers positive and negative X and Y crossings.
- TS-2 is retired. AP-83 and AP-91 remain because ordinary missiles are
PathClipped and do not arm their dormant PerfectClip time-of-impact paths.
## 16. Step 7 live integration map (2026-07-14)
The App integration preserves the same retail object across every spatial and
network operation:
```text
after materialized CreateObject:
if final PhysicsState has never carried Missile: no projectile component
if Setup does not have the audited one ordinary sphere: diagnose, no guess
otherwise:
construct one PhysicsBody, or adopt the same body already owned by the
object's MovementManager
validate the inbound frame/vectors, canonicalize the outdoor cell,
retain valid friction in [0,1], and clamp elasticity to [0,0.1]
apply velocity through set_velocity (immediate 50-unit/second clamp)
bind it to LiveEntityRecord.ProjectileRuntime
when MovementManager and projectile classification arrive in either order:
bind MovementManager cell reads/writes to this exact live-record incarnation
share its PhysicsBody with ProjectileRuntime
install CreateObject velocity/omega when the ordinary body is constructed
if projectile classification arrives second, preserve that body's CURRENT
vectors/Active state, validate that current body rather than stale
CreateObject vectors, normalize its current cell frame, and translate
its clock to the App game clock without replaying retained vectors;
a safely ignored malformed CreateObject vector cannot veto a later
finite body, while a non-finite current body cannot enter simulation
each frame:
for each canonical live record with a projectile component:
skip/clear Active through update_object when parented, cell-less,
Frozen, Static, or out of world
if the object has a PartArray, clear Active beyond the retail
96-world-unit player-distance boundary; reactivation stamps now
run ProjectilePhysicsStepper with local WorldEntity.Id for self-shadow
exclusion and target id zero
if this exact record generation still owns this exact component:
WorldEntity.SetPosition(resolved body frame)
update orientation and full parent cell
RebucketLiveEntity only when the full cell changed
move the existing collision shadow to the resolved frame
publish the new root to the effect-pose owner
if Missile is now clear and MovementManager exists: that ordinary owner
advances the shared body instead
if Missile is clear with no MovementManager: keep advancing the active
retained CPhysicsObj; collision reads final state, so missile_ignore
and Missile-derived PathClipped are disabled automatically
fresh State/Vector/Position/Movement packet:
reject structurally invalid Position/Vector values before advancing the
snapshot or timestamp, even if Missile classification has not arrived
first pass the existing per-channel retail timestamp gate
State: update the canonical body first, even when missile acquisition is
impossible because Setup or sphere data is unavailable; the accepted
SetState collision flags must never remain only on the live record.
A non-finite local receipt clock does not discard first classification:
bind against the controller's last finite game clock (the clock epoch
is zero before the first frame), while keeping the wire state authoritative.
Update this body without newly activating it; removing Missile
disables missile classification but retains the component/body on this
object. MovementManager becomes the update owner when present;
otherwise the retained active CPhysicsObj continues ordinary physics.
A later Missile state reuses that same current body and cell;
align the projectile clock at that ownership transfer, preserve the
existing Active bit, and never activate a body that was already inactive
Vector: set_velocity + omega on this body; clamp immediately and stamp the
receipt clock only when the packet reactivates an inactive object; if
Missile is currently clear and MovementManager exists, its ordinary
Airborne/LeaveGround funnel owns those writes on the shared body
Position: hard-correct world frame plus canonical wire cell-local frame,
shadow, spatial bucket, and effect pose; do not overwrite velocity
Movement: unpack through the normal MovementManager/animation funnel while
sharing this exact PhysicsBody and the live record's canonical cell;
projectile stepping suppresses the generic remote translation tick,
so there is still only one integrator and one spatial authority
leave_world (pickup/parent):
clear InWorld and Active, withdraw collision shadows but retain their exact
registration payload and the component with the incarnation
fresh Position re-entry restores the same shadows, stamps the current
physics clock, and never consumes the time spent outside the world
loaded/pending transition:
the rebucket result is checked in the same physics quantum
pending clears InWorld/Active and suspends shadows without teardown
hydration restores body/shadow membership even if Missile was cleared while
pending; projectile integration itself remains disabled
landblock rehydration keys materialized movers by LiveEntityRecord.FullCellId,
never by the retained create-time Snapshot.Position
DeleteObject / generation replacement / session reset:
normal LiveEntityRuntime teardown destroys the owning record; the
projectile controller has no second GUID dictionary to clear
```
`ProjectileController` runs before animation advancement in the render/update
phase and publishes its current root directly; an animated projectile then
publishes final part poses later in the same frame. PhysicsScript/particle/light
ticks therefore observe the projectile's current predicted root. The controller never
creates impact effects, collision messages, damage, or deletion; ACE's later
packets remain authoritative for every gameplay outcome.