acdream/docs/research/2026-07-26-slice-j5-5-physics-remote-ownership.md
Erik 7e6033d0ad refactor(runtime): own per-session physics simulation
Move the sole PhysicsEngine, production cache, collision admissions, canonical bodies and hosts, remote components, ordinary/remote worksets, simulation, cell commits, and shadow synchronization under RuntimeEntityObjectLifetime. Keep App as the prepared-asset, animation-input, and render-projection adapter while preserving the named-retail update and collision order.

Add exact-incarnation, object-clock, callback-reentrancy, GUID-reuse, two-runtime isolation, source ownership, collision publication, and graphical projection coverage. Release build and the complete 8,588-test solution pass.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 13:39:57 +02:00

200 lines
8.8 KiB
Markdown

# Slice J5.5 — per-session physics and remote-simulation ownership
Date: 2026-07-26
Scope: ownership relocation only; no retail algorithm or gameplay-policy change
## Objective
Make one `AcDream.Runtime` instance own one complete mutable physics world:
- one `PhysicsEngine`, its transition-scratch arena, and its shadow registry;
- one production `PhysicsDataCache`/cell graph;
- canonical physics bodies and physics hosts;
- canonical remote movement/interpolation state;
- loaded/cell-backed ordinary and remote update worksets.
App remains responsible for DAT/package preparation, streaming policy, animation
pose production, render projection, effects, meshes, debug drawing, and UI. It
publishes immutable prepared collision records and animation-root inputs to the
Runtime owner, then applies committed Runtime snapshots to graphical objects.
This is the J5.5 boundary from
`docs/plans/2026-07-26-modern-runtime-slice-j5.md`. Projectile component
ownership and prediction remain explicitly deferred to J5.6.
## Named-retail oracle
Primary source:
`docs/research/named-retail/acclient_2013_pseudo_c.txt`.
- `CPhysics::UseTime` `0x00509950`
- `CPhysicsObj::UpdatePositionInternal` `0x00512C30`
- `CPhysicsObj::transition` `0x00512DC0`
- `CPhysicsObj::SetPositionInternal(CTransition const*)` `0x00515330`
- `CPhysicsObj::UpdateObjectInternal` `0x005156B0`
- `CPhysicsObj::update_object` `0x00515D10`
- `CPhysicsObj::MoveOrTeleport` `0x00516330`
- `CPhysicsObj::add_shadows_to_cells` `0x00514AE0`
The complete translated control flow is already pinned in
`docs/research/2026-07-19-r6-update-object-order-pseudocode.md`. The current
Core/App implementation was cross-checked there and in the R6/physics digest
against ACE's `PhysicsObj` interpretation and the extracted WorldBuilder
collision/content pipeline. J5.5 reuses that port unchanged.
## Preserved pseudocode
```text
for each ordinary object in the active object table:
elapsed = current physics time - object update time
apply retail epsilon / stale-quantum / subdivision gates
if object is Active:
if not Hidden:
advance PartArray and obtain the complete root-motion Frame
apply PositionManager correction to that Frame
compose the candidate position and orientation
if not Hidden:
integrate acceleration, velocity, and omega
drain object animation hooks
if the candidate frame moved:
run Transition against the object's current full cell
commit accepted frame, contact state, and full cell
update the object's collision shadows
DetectionManager.CheckDetection
TargetManager.HandleTargetting
MovementManager.UseTime
PartArray.HandleMovement
PositionManager.UseTime
ParticleManager.UpdateParticles
ScriptManager.UpdateScripts
```
The App/Runtime seam may split pose production from simulation, but it must not
change this observable ordering. In particular:
- root motion and the position-manager delta are complete before collision;
- the transition's swept full cell is canonical;
- a Hidden object is not the same as an inactive or deleted object;
- exact record/incarnation, authority version, and object-clock epoch are
revalidated after every callback;
- remote shadows follow the committed body frame, never a raw server sample;
- `CellGraph.CurrCell` remains local-player-only state.
## Baseline ownership gap
The canonical `RuntimeEntityRecord` already owns `PhysicsBody`,
`IPhysicsObjHost`, full cell, final physics state, accepted authority versions,
and `RetailObjectQuantumClock`. The remaining duplicate boundary is:
- `GameWindow` constructs the session `PhysicsEngine` and production
`PhysicsDataCache`;
- App `LiveEntityRecord` stores the remote-motion component;
- App `LiveEntityRuntime` stores remote and ordinary spatial workset indexes;
- App updaters both mutate canonical simulation and project `WorldEntity`;
- App streaming mutates the engine directly.
That baseline shape prevented a no-window Runtime from owning the same
simulation graph and made two simultaneous sessions harder to prove isolated.
J5.5 retires this gap.
## Target ownership
`RuntimeEntityObjectLifetime` constructs one `RuntimePhysicsState` beside its
entity directory. `RuntimePhysicsState` owns:
- the exact `PhysicsEngine` and production cache;
- remote components and physics hosts stored on `RuntimeEntityRecord`;
- keyed ordinary/remote worksets containing exact `RuntimeEntityKey` values;
- typed collision admission/withdrawal receipts;
- simulation commits and immutable presentation snapshots;
- reset/disposal convergence and an ownership ledger.
App acknowledgements contain no gameplay policy:
```text
prepared collision publication -> Runtime typed admission -> receipt
render spatial visibility edge -> Runtime workset acknowledgement
animated root Frame -> Runtime remote/ordinary tick
Runtime committed snapshot -> App WorldEntity/rebucket/pose projection
```
The immutable prepared collision payload may originate in App streaming. The
engine, transition scratch, cell graph, body, shadow registry, interpolation,
manager state, and workset are never shared across Runtime instances.
## Implemented boundary
- `RuntimeEntityObjectLifetime` constructs and disposes exactly one
`RuntimePhysicsState`.
- `RuntimePhysicsState` constructs the production `PhysicsDataCache` and the
sole `PhysicsEngine`, including its ten-deep reusable transition scratch and
shadow registry. App borrows both objects and cannot construct alternatives.
- `RuntimeEntityRecord` owns the canonical `PhysicsBody`, `RemoteMotion`,
`EntityPhysicsHost`, full cell, object clock, and exact-incarnation binding
guards.
- `RuntimePhysicsState` owns keyed ordinary/remote worksets, body acquisition,
host installation/rebinding/lookup, remote component binding, and typed
canonical cell commits.
- `RuntimeRemotePhysicsUpdater` and `RuntimeOrdinaryPhysicsUpdater` contain the
unchanged presentation-free retail simulation. Their App adapters supply
DAT-derived shape/animation inputs and project immutable pose snapshots only.
- `LandblockPhysicsPublisher` publishes the prepared collision root through a
generation-scoped `RuntimeCollisionAdmission`; demotion and withdrawal cross
the same typed Runtime boundary.
- Runtime cell commits synchronously publish
`RuntimePhysicsCellCommit`. The graphical subscriber revalidates the exact
record and spatial-authority version before rebucketing the render
projection.
- Two Runtime instances own distinct engines, caches, cell graphs, scratch
arenas, shadow registries, components, hosts, bodies, and worksets.
No retail algorithm, constant, order, packet, or gameplay policy changed, and
this ownership relocation introduces no retail-divergence row.
## Staged implementation boundary
1. Construct engine/cache under `RuntimePhysicsState`; make App borrow them.
2. Move presentation-free `RemoteMotion` and `EntityPhysicsHost` types into
Runtime and store the remote component on `RuntimeEntityRecord`.
3. Move remote/ordinary keyed worksets and their visibility acknowledgements
into `RuntimePhysicsState`.
4. Split remote/ordinary update into Runtime simulation commits and App
presentation acknowledgements without changing update order.
5. Replace direct streaming mutation with typed Runtime
admission/withdrawal receipts.
6. Remove App simulation mirrors and prove reset/two-instance isolation.
Each intermediate state must build and retain exact behavior. The production
commit remains independently revertible as the single J5.5 ownership unit.
## Acceptance evidence
- existing flat-collision conformance corpus remains bit-identical;
- reused versus fresh transition scratch stays equivalent and allocation-free;
- local and remote position/cell/orientation fixtures remain exact;
- crowd, doors, stairs, cell seams, Hidden, teleport, delete, and GUID-reuse
fixtures pass;
- two Runtime instances have distinct engine, cache, scratch, shadows, bodies,
hosts, remote components, and worksets;
- App production source cannot construct a `PhysicsEngine`,
`PhysicsDataCache`, canonical remote component, or simulation workset;
- Release build, focused Runtime/App suites, complete Release suite, connected
lifecycle/reconnect, and the canonical movement route pass.
Automated gate on the J5.5 production tree:
- Release solution build: zero warnings and zero errors.
- Core collision/physics suite: 3,273 passed / 2 skipped.
- Runtime suite: 314 passed.
- App suite: 3,718 passed / 3 skipped.
- Complete solution: 8,588 passed / 5 skipped.
- Source ownership guards prove App does not construct a `PhysicsEngine` or
production cache, does not contain the remote/ordinary transition solver,
and cannot directly admit/remove streamed landblock collision.
Connected lifecycle/reconnect and the canonical movement/collision route are
the exact-binary post-commit gate.