acdream/docs/research/2026-07-26-slice-j5-6-projectile-ownership.md

7.2 KiB

Slice J5.6 — projectile-simulation ownership

Date: 2026-07-26 Scope: ownership relocation only; no retail algorithm, packet, collision policy, impact authority, or presentation change

Objective

Move the mutable projectile component, projectile workset, prediction version, authoritative correction, and retail projectile step into the same AcDream.Runtime physics lifetime that already owns the canonical entity record, body, object clock, collision engine, shadows, and full cell.

App remains responsible for:

  • resolving the projectile's immutable Setup/DAT collision sphere;
  • supplying the current graphical projection validity acknowledgement;
  • rebucketing and positioning the retained render projection after a Runtime commit;
  • synchronizing the render shadow and effect pose;
  • drawing meshes, trails, particles, sounds, and impact effects.

ACE remains authoritative for targets, hits, damage, impact scripts, and deletion.

Named-retail oracle

Primary source: docs/research/named-retail/acclient_2013_pseudo_c.txt.

  • CPhysicsObj::UpdateObjectInternal 0x005156B0
  • CPhysicsObj::update_object 0x00515D10
  • CPhysicsObj::SetPositionInternal(CTransition const*) 0x00515330
  • CPhysicsObj::set_velocity 0x005113F0
  • OBJECTINFO::missile_ignore 0x0050CEB0
  • Frame::set_vector_heading 0x00535DB0

The complete translated retail projectile control flow is pinned in docs/research/2026-07-13-retail-projectile-vfx-pseudocode.md. The existing Core ProjectilePhysicsStepper and collision driver were cross-checked there against ACE's physics implementation and Holtburger/ACViewer's DAT interpretation. J5.6 reuses those algorithms unchanged.

Preserved control flow

for each spatial projectile owned by the current incarnation:
    reject static, frozen, parented, hidden, cell-less, or inactive bodies
    advance the retained CPhysicsObj clock in retail 0.2-second quanta
    for each due quantum:
        calculate acceleration from the final PhysicsState
        clamp speed to 50 world units/second
        integrate position, velocity, omega, and AlignPath orientation
        continuously sweep the authored Setup-local collision sphere
        apply retail missile-ignore and inelastic/elastic collision rules
        commit the accepted frame and full cell to the canonical record
        stop the split prediction if any authority/incarnation token changed
    publish one immutable committed frame to the host

The graphical host may reject the presentation acknowledgement after a re-entrant delete, replacement, rebucket, or session reset. That rejection invalidates the pending prediction; it cannot roll back or redirect a newer canonical owner.

Previous ownership gap

Before J5.6:

  • RuntimeEntityRecord owned identity, body, state, clocks, and cell;
  • RuntimePhysicsState owned the engine, shadows, remotes, and ordinary/remote worksets;
  • App ProjectileController still owned the projectile component, projectile workset snapshot, quantum stepping, correction, and canonical cell commit.

That split made a no-window Runtime unable to fly the same projectile without reconstructing App behavior and left projectile mutation on the wrong side of the presentation boundary.

Implemented boundary

  • RuntimeEntityRecord.Projectile is the sole mutable projectile component slot.
  • RuntimeProjectile owns the canonical body, authored collision sphere, and prediction-authority version.
  • RuntimePhysicsState owns binding, refresh, spatial membership, authoritative vector/state correction, and the exact-key projectile workset.
  • RuntimeProjectilePhysicsUpdater owns the unchanged retail quantum, integration, sweep, collision, cell-crossing, and split-prediction logic.
  • Runtime cell commits flow through the same typed RuntimePhysicsCellCommit used by ordinary and remote objects.
  • App ProjectileController resolves DAT/Setup shape data, delegates the simulation, and applies only retained render projection, shadow, and effect pose acknowledgements.
  • Delete, GUID reuse, visibility withdrawal, pending-to-loaded recovery, authoritative correction, and session teardown use the canonical Runtime record and workset; they never reconstruct from stale spawn data.

No gameplay outcome moved client-side. No projectile-specific render pass, second GUID map, body mirror, effect owner, or collision owner was introduced.

Automated acceptance

On production commit 2aee33569f0268d7bc0f4f52437dfa0b405432a4:

  • Runtime tests: 317 passed.
  • App tests: 3,718 passed / 3 skipped.
  • Focused Core projectile tests: 47 passed.
  • Complete Release solution: 8,591 passed / 5 skipped.
  • Release solution build: zero warnings and zero errors.

The focused fixtures cover:

  • canonical component/body/workset identity;
  • 0.2-second catch-up quanta and prediction invalidation;
  • clamp, gravity, omega, full 3-D AlignPath, and collision;
  • thin walls and inelastic stopping;
  • missile/ethereal/designated-target exclusion;
  • loaded, pending, landblock crossing, and hydration;
  • State/Vector/Position correction order;
  • deletion, same-GUID reuse, reset, and 96-owner lifetime stress;
  • no duplicate mesh, body, shadow, script, effect, or projectile owner.

Connected acceptance

All connected processes used the exact embedded/source commit 2aee33569f0268d7bc0f4f52437dfa0b405432a4 and closed gracefully.

  • Arrow plus representative spell projectile: .test-out/j56-projectile-20260726-143256 recorded an authoritative missile attack, live Arrow spawn, live Acid Stream spawn, and matching authoritative deletes.
  • Crossbow bolt: .test-out/j56-crossbow-evidence-20260726-145612 recorded a missile attack, live Quarrel spawn with Setup 0x0200012A, authoritative ammo pickup/update, projectile delete, and target death.
  • Lifecycle/reconnect: logs/connected-world-gate-20260726-145731/report.json passed capped login plus fresh uncapped reconnect, all seven checkpoints, zero failures, and two code-zero exits.
  • Canonical nine-stop route: logs/connected-r6-soak-20260726-151349.report.json materialized all nine destinations, passed the Caul/Holtburg/Caul-return forward, jump, combat, and movement-truth exercises, matched source and binary, and exited with code zero and zero failures.

The first route attempt, logs/connected-r6-soak-20260726-150347.report.json, intentionally remains in the evidence trail. Its strict plateau check saw the already-known late lazy mesh population (582 -> 604) while sampled entity counts happened to match and therefore reported one failure. The immediate warm-machine rerun saw the same class of late population (581 -> 598) together with one changed visible owner and correctly classified it as workload variation. The accepted J5.5 baseline exhibited the same late Caul behavior (575 -> 597), and the J5.6 diff contains no mesh-cache, mesh-preparation, streaming, or residency change. This is not waived as a projectile result and no threshold was loosened.

Divergence and rollback

J5.6 changes ownership only. The retail algorithm, constants, packet order, server authority, and presentation are unchanged, so it introduces no new retail-divergence row.

Exact rollback:

git revert 2aee33569f0268d7bc0f4f52437dfa0b405432a4