feat(physics): port retail complete object frame pipeline

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>
This commit is contained in:
Erik 2026-07-20 09:10:31 +02:00
parent 31a0889f08
commit f961d70023
77 changed files with 12513 additions and 1871 deletions

View file

@ -4,6 +4,15 @@
**Source:** `docs/research/named-retail/acclient_2013_pseudo_c.txt` (Sept 2013 EoR PDB-named decomp).
**Cross-check:** `src/AcDream.Core/Physics/InterpolationManager.cs` (current port), `docs/research/2026-05-02-remote-entity-motion/resolved-via-cdb.md` (cdb-traced).
> **2026-07-19 correction:** the May audit correctly identified that
> `adjust_offset` mutates its `Frame*`, but incorrectly described the result as
> translation-only and the old `Vector3` API as equivalent. Retail preserves
> the complete relative quaternion produced by `Position::subtract2` unless
> `keep_heading` explicitly replaces that rotation with heading zero. R6 now
> carries the complete Frame. See
> `docs/research/2026-07-19-r6-complete-root-frame-pseudocode.md`. Section 7 is
> retained as a historical snapshot of the May port, not current status.
---
## 1. Class layout (verbatim from `acclient.h`)
@ -135,7 +144,9 @@ Our current port does duplicate-prune against only the last entry (`_queue.Last`
## 4. `InterpolationManager::adjust_offset` @ `0x00555d30`
Signature: `void adjust_offset(InterpolationManager *this, Frame *arg2, double arg3)`
- `arg2` = output Frame (already-zeroed identity Frame from caller `CPhysicsObj::UpdatePartsInternal` @ `0x00512c3c`).
- `arg2` = the mutable per-object delta Frame. `CPartArray::Update` may already
have written root translation and orientation into it; active interpolation
replaces that complete Frame.
- `arg3` = frame `dt` in seconds (double).
### Critical answer: **arg2 is MUTATED IN PLACE.** Not a delta-return.
@ -146,13 +157,18 @@ The last meaningful action (line 353253) is:
00555f10 Frame::operator=(arg2, &__return);
```
where `__return` is a Frame whose `m_fOrigin` was just scaled to the per-frame step, and whose rotation is 0 (or kept-heading) (line 353251). The caller composes this into the world position with:
where `__return` is the complete relative Frame from `Position::subtract2`.
Only `m_fOrigin` is scaled to the per-frame step. Its quaternion remains the
target-relative rotation unless `keep_heading` calls `Frame::set_heading(0)`.
The caller composes this into the world position with:
```c
00512d22 Frame::combine(arg3 /*world out*/, &this->m_position.frame, &var_40 /*= arg2*/);
```
**So `adjust_offset` writes a translation-only Frame — the caller treats it as an offset Frame to combine with the body's current position.** Our acdream port returns a `Vector3` delta, which the caller adds to the body — equivalent semantics.
**So `adjust_offset` writes a complete relative Frame, and the caller combines
both its translation and orientation with the body's current Frame.** A
`Vector3`-only port loses pitch/roll and is not equivalent.
### Step-by-step retail flow:
@ -238,7 +254,7 @@ if (this->keep_heading != 0) {
}
// ---- Output ----
Frame::operator=(arg2, &delta_frame); // OUT: arg2 = translation-only Frame
Frame::operator=(arg2, &delta_frame); // OUT: complete relative Frame
```
### NOTE on `progress_quantum`
@ -456,7 +472,7 @@ if (CPhysicsObj::SetPositionSimple(po, &target, 1) == OK_SPE) {
| **Stall fail action** | Increments fail counter; only blips when threshold exceeded INSIDE adjust_offset | Calls `NodeCompleted(0)` on stall and pops the head; UseTime does the actual blip | **Architectural gap.** Retail's NodeCompleted(0) on stall pops the head node (advancing the queue) — useful when the head is unreachable but later nodes might be. Our port leaves the head in place. This means a single bad waypoint can cause repeated 5-frame failures rather than skipping past it. |
| Blip target on threshold | `_queue.Last.TargetPosition` (tail) | UseTime: tail OR blipto_position OR last-pos-before-velocity-tail | Mostly OK for our use case (only type-1 nodes). |
| `transient_state & 1` gate | None | Required | Probably safe in our codebase; file as follow-up. |
| AdjustOffset return shape | `Vector3` delta | In-place mutate `Frame*` (translation-only Frame) | Functionally equivalent — caller composes with current position either way. |
| AdjustOffset return shape | `Vector3` delta | In-place mutate complete `Frame*` | **Historical R6 gap, fixed 2026-07-19.** The old shape lost target-relative orientation; production now uses `MotionDeltaFrame`. |
### Concrete actionable changes