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:
parent
f02b995e4f
commit
a51ebc66e9
12 changed files with 2559 additions and 32 deletions
|
|
@ -179,6 +179,8 @@ src/
|
|||
ISelectionService.cs -> done
|
||||
|
||||
AcDream.App/ Layer 1 + Layer 4 wiring
|
||||
Physics/
|
||||
ProjectileController.cs -> live-record projectile orchestration/corrections
|
||||
Rendering/
|
||||
GameWindow.cs -> still owns too much runtime wiring
|
||||
TerrainRenderer.cs -> done
|
||||
|
|
@ -224,6 +226,28 @@ What exists and is active:
|
|||
AlignPath, Setup-sphere transition sweeps, and collision response. It owns no
|
||||
live entity, network, rendering, or App state; the App controller supplies
|
||||
those boundaries.
|
||||
- `ProjectileController` is that App boundary. It installs one body on the
|
||||
canonical `LiveEntityRecord`, advances it on the update/render thread,
|
||||
commits through `WorldEntity.SetPosition`, publishes the effect root,
|
||||
synchronizes the existing shadow, and calls `RebucketLiveEntity` only when
|
||||
the full cell changes. Pending/leave-world residence suspends the shadow's
|
||||
cell rows while retaining its registration for exact re-entry. Timestamp-
|
||||
gated State/Vector/Position/Movement packets correct or stop that same body;
|
||||
every production MovementManager delegates full-cell reads/writes to the
|
||||
incarnation-scoped live record, and projectile acquisition adopts its body
|
||||
without replaying vectors already installed when that body was created,
|
||||
regardless of component creation order. Accepted SetState flags reach that
|
||||
canonical body before optional projectile acquisition, so absent/unsupported
|
||||
DAT shape data cannot leave collision state stale; a non-finite local receipt
|
||||
clock uses the controller's last finite game-clock value and cannot consume
|
||||
first-time Missile classification. Shared-body acquisition validates the
|
||||
adopted body's current frame/vectors, not obsolete CreateObject inputs.
|
||||
Initial and late Setup resolution share GameWindow's `_datLock`, preserving
|
||||
the single-reader `DatCollection` invariant while streaming is active.
|
||||
Generic remote translation is suppressed while projectile integration
|
||||
owns the frame;
|
||||
delete, generation replacement, pickup/parent leave-world, and session reset
|
||||
use `LiveEntityRuntime`'s normal lifecycle and never create a second GUID map.
|
||||
- `BSPQuery` contains the partial retail-style BSP collision dispatcher used by
|
||||
the transition path.
|
||||
- `TransitionTypes` carries `SpherePath`, `CollisionInfo`, `ObjectInfo`,
|
||||
|
|
|
|||
|
|
@ -516,6 +516,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- ~~**D.8 — Sound.**~~ **Superseded — shipped as Phase E.2** (`SoundTable`/`Sound` dat decode, OpenAL 16-voice engine, per-entity 3D positional audio via `AudioHookSink`). Entry kept here for history; see the shipped table.
|
||||
|
||||
- **Missile/portal VFX campaign Step 6 implemented and independently reviewed 2026-07-14.** Pure Core now owns the retail projectile clock/integration/sweep primitive: 0.2-second catch-up quanta, 50-unit/second clamp, final-state acceleration, world-space omega, complete 3-D AlignPath, scaled Setup-local collision spheres, terrain/BSP/object continuous sweeps, exact `OBJECTINFO::missile_ignore`, self-shadow rejection, elastic/inelastic response, and correct full-cell rebasing across loaded landblocks. App live ownership and authoritative corrections remain Step 7. TS-2 is retired; ordinary missiles add PathClipped but not PerfectClip, so AP-83/AP-91 remain dormant.
|
||||
- **Missile/portal VFX campaign Step 7 implemented and independently reviewed 2026-07-14.** `ProjectileController` now attaches the Step 6 body to the canonical `LiveEntityRecord` after materialization and advances arrows, bolts, and spell missiles without another GUID map, renderer registration, or stale-spawn reconstruction. It commits predicted frames through `WorldEntity.SetPosition`, republishes static effect roots, and keeps collision shadows plus canonical full-cell buckets synchronized. A predicted crossing into an unloaded bucket suspends the body and shadow in that same quantum; pending/pickup residence retains the shadow registration for exact re-entry, and hydration plus the 96-unit retail activity gate restart at the current clock without backlog. Fresh State/Vector/Position/Movement packets correct or stop the same body after retail timestamp gates; SetState reaches the canonical body before optional projectile acquisition, and a non-finite local receipt clock cannot consume first classification. Removing Missile retains ordinary active-body physics (delegating to MovementManager when present), while both owners share the body and incarnation-scoped live-record cell. CreateObject vectors are installed only at body construction, never replayed by later SetState. Delete, reset, and GUID reuse use normal logical teardown; ACE remains authoritative for impact, damage, effects, and deletion.
|
||||
|
||||
**Reference docs:** `docs/research/retail-ui/00-master-synthesis.md` + slices 01-06. Every AC-specific behavior has a decompiled FUN_ / DAT_ citation.
|
||||
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ include dungeons.
|
|||
`PlayerDescription.Options1` preserves retail's player secure-trade
|
||||
preference. See
|
||||
`docs/research/2026-07-13-retail-give-item-pseudocode.md` and issue #216.
|
||||
- **M2/M3 missile, effect, and portal presentation campaign (Steps 0–6
|
||||
- **M2/M3 missile, effect, and portal presentation campaign (Steps 0–7
|
||||
implemented and independently reviewed 2026-07-14)** — named-retail projectile and
|
||||
physics-script behavior is pinned in one oracle, the complete `PhysicsDesc`
|
||||
and F754/F755 wire surfaces are parsed with retail timestamp gates, and
|
||||
|
|
@ -515,9 +515,19 @@ include dungeons.
|
|||
Setup-local sphere sweeps, exact missile target/ethereal exclusions,
|
||||
self-shadow rejection, retail collision response, and full-cell landblock
|
||||
rebasing. Missile adds PathClipped but never PerfectClip. AP-67, TS-2, TS-10,
|
||||
TS-11, and TS-12 are retired; AP-83/AP-91 remain dormant. The remaining
|
||||
steps integrate live projectile ownership/corrections and port Hidden/UnHide
|
||||
portal presentation.
|
||||
TS-11, and TS-12 are retired; AP-83/AP-91 remain dormant. Step 7 attaches
|
||||
that primitive to the canonical `LiveEntityRecord`: arrows, bolts, and spell
|
||||
missiles share one body and one renderer identity across prediction,
|
||||
authoritative State/Vector/Position/Movement corrections, full-cell
|
||||
rebucketing, pending-landblock residence, delete, session reset, and GUID
|
||||
reuse. MovementManager presentation shares the same body and the canonical
|
||||
live-record cell regardless of which component was created first;
|
||||
pending/leave-world shadows retain their registration for exact re-entry even
|
||||
if Missile classification changes while pending; static missile effect
|
||||
roots follow every predicted frame, and materialized rehydration never uses a
|
||||
stale spawn cell. No impact, damage, effect, or delete event is synthesized
|
||||
client-side. The remaining steps port
|
||||
Hidden/UnHide portal presentation and run final hardening/visual gates.
|
||||
- **L.1c local attack receive path (implemented 2026-07-11; live gate pending)** —
|
||||
local non-autonomous mt-0 UpdateMotion now uses retail's wholesale interpreted
|
||||
funnel and action-stamp gate, so ACE's server-selected melee/missile action
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue