fix(#170): armed moveto always ticks UseTime — the SERVERVEL branch starved the chase
The "sustain the run" residual. The handoff's "Ready stop-node backlog drains a beat slower than retail" framing was DISPROVEN: a new full-stack offline harness (RemoteChaseEndToEndHarnessTests — real MoveToManager + MotionInterpreter + AnimationSequencer + MotionTableDispatchSink + the manual omega integration, wired field-for-field like EnsureRemoteMotionBindings and ticked in TickAnimations' exact phase order) proves the Core turn/run/drain pipeline healthy: the chase turn completes in <1 s both directions, BeginMoveForward installs per arm, the run sustains across re-arms and attack swings, and pending_motions fully empties (retail cdb invariant add_to_queue == MotionDone). The real mechanism (launch-drainq.log, corrected per-guid attribution — the previous session's timeline mis-attributed [mvto] lines that fire in the network phase): funnel per chasing scamp was 16 mt-6 arms -> 11 dispatched turns -> ONE BeginMoveForward. Any NPC receiving UpdatePositions gets HasServerVelocity=true (synthesized from position deltas even when the wire carries no velocity), and the grounded per-tick branch routed those to the SERVERVEL leg, which SKIPS MoveToManager.UseTime — [npc-tick] literally logged "branch=SERVERVEL (skips UseTime) mtState=MoveToObject". The armed moveto was starved for exactly the duration of the server-side chase: legs stayed in Ready while the body glided on synthesized velocity (the #170 slide); the manager only woke in UP-silent gaps (creature stopped server-side) and its stale-heading turn was interrupted by the next UM before reaching BeginMoveForward. Retail runs MovementManager::UseTime UNCONDITIONALLY every tick (CPhysicsObj::UpdateObjectInternal 0x005156b0, call @0x00515998) and has no wire-velocity leg-driver anywhere; between UPs a moveto-driven body translates from the motion state (get_state_velocity) with UP hard-snaps correcting drift. Fix: an armed moveto (MovementTypeState != Invalid) always takes the MOVETO leg; SERVERVEL remains only as the legacy fallback for entities without a moveto (scripted paths / missiles). Register: TS-41 (the narrowed SERVERVEL stopgap), TS-42 (drain-order divergence also pinned this session: acdream drains AnimDone->MotionDone AFTER HandleTargetting/MoveTo.UseTime; retail's process_hooks @0x00512d3d runs BEFORE TargetManager/MovementManager in UpdateObjectInternal — one frame of extra latency, R6 scope). New conformance: RemoteChaseEndToEndHarnessTests (3 scenarios + theory) + RemoteChaseDrainBisectTests (the drain-chain pin; its first run also demonstrated the TS-40 InWorld=false link-strip wedge shape — harness bodies must replicate the live RemoteMotion construction). ISSUES #170 updated (awaiting user visual gate; probes stay until then); handoff doc superseded-note added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e744192361
commit
1051fc83c6
6 changed files with 811 additions and 20 deletions
|
|
@ -48,25 +48,33 @@ Copy this block when adding a new issue:
|
|||
|
||||
## #170 — Remote creature chase+attack renders wrong vs retail (glide, over-frequency, uniform attack anims)
|
||||
|
||||
**Status:** OPEN — **PARTIAL FIX landed `427332ac` (2026-07-04); residual = "sustain the run".**
|
||||
Root-caused end-to-end with live retail cdb tracing + acdream probes. The chase run
|
||||
cycle is manufactured client-side from mt-6 (`HandleUpdateTarget → MoveToObject_Internal
|
||||
→ TurnToHeading node completes via UseTime → BeginMoveForward → RunForward substate`);
|
||||
`BeginTurnToHeading` bails while `CMotionInterp.motions_pending` is non-empty
|
||||
(retail-faithful). acdream's `pending_motions` EXPLODED to ~1.3M entries because the
|
||||
NPC per-tick called `apply_current_movement` EVERY FRAME (re-dispatching stance+attack+
|
||||
stops), so `MotionsPending()` stayed permanently true → the chase turn never started →
|
||||
no RunForward → slide. FIX = delete the per-frame `apply_current_movement` (GameWindow
|
||||
~9992; retail dispatches per UM, `add_to_queue==MotionDone`). VERIFIED: flood 1.3M→~1,
|
||||
"stuck attack" GONE (user-confirmed), run installs (BeginMoveForward 1→10). **RESIDUAL
|
||||
(next session):** still blocked motionsPending=True 94% by a `Ready` (0x41000003)
|
||||
stop-node backlog that drains a beat slower than retail (~10 run-starts vs retail 21 →
|
||||
twitches+glides). Fix the R2/R3 `Ready`-stop DRAIN so acdream fully empties like retail
|
||||
(retail is the oracle — cdb the drain). **SSOT + pickup:**
|
||||
**Status:** OPEN — **residual fix landed 2026-07-04 ("sustain the run" — SERVERVEL
|
||||
starvation); AWAITING VISUAL GATE.** Fix #1 (`427332ac`): the per-frame
|
||||
`apply_current_movement` re-dispatch flooded `pending_motions` to ~1.3M → deleted;
|
||||
flood 1.3M→~1, "stuck attack" gone (user-confirmed), run installs 1→10. Fix #2 (this
|
||||
session): **the "slow Ready drain" framing was WRONG** — a full-stack offline harness
|
||||
(`RemoteChaseEndToEndHarnessTests`, real MoveToManager + MotionInterpreter +
|
||||
AnimationSequencer + omega integration in GameWindow's exact tick order) proved the
|
||||
Core drain/turn/run pipeline healthy (turn completes <1 s, run sustains, add==done).
|
||||
Corrected per-guid log attribution (launch-drainq.log) showed the REAL funnel: 16 arms
|
||||
→ 11 turns dispatched → **1 run install**, because the per-tick branch arbitration
|
||||
routed any UP-receiving NPC to the SERVERVEL leg (`HasServerVelocity` synthesized from
|
||||
position deltas) which **skips `MoveToManager.UseTime`** — the armed moveto was starved
|
||||
for exactly the duration of the server-side chase (`[npc-tick] branch=SERVERVEL (skips
|
||||
UseTime) mtState=MoveToObject`), legs stayed Ready while the body glided on synthesized
|
||||
velocity; the manager only woke in UP-silent gaps and was interrupted by the next UM.
|
||||
Retail runs `MovementManager::UseTime` UNCONDITIONALLY per tick
|
||||
(`CPhysicsObj::UpdateObjectInternal` 0x005156b0 @0x00515998) and has no wire-velocity
|
||||
leg-driver. FIX: an armed moveto (`MovementTypeState != Invalid`) always takes the
|
||||
MOVETO leg; SERVERVEL remains only for non-moveto entities (register TS-41; drain-order
|
||||
one-frame divergence also pinned + filed as TS-42). **SSOT + pickup:**
|
||||
`docs/research/2026-07-04-170-creature-run-handoff.md` +
|
||||
`docs/research/2026-07-04-170-pickup-prompt.md`. SUPERSEDES the earlier
|
||||
MovementManager-coexistence hypothesis (`eb423fb7`, wrong) and the velocity fix
|
||||
(`d2ccc80e`, correct but position-only). #159 was a red herring here.
|
||||
`docs/research/2026-07-04-170-pickup-prompt.md` (residual sections superseded by this
|
||||
entry). SUPERSEDES the earlier MovementManager-coexistence hypothesis (`eb423fb7`,
|
||||
wrong shape — but the starvation IS a coexistence bug at the tick-arbitration altitude)
|
||||
and keeps the `d2ccc80e` velocity fix. #159 was a red herring here. NEXT: user visual
|
||||
gate (retail side-by-side, chase a fleeing player) → then strip the #170 probes
|
||||
(`s_mvtoDiag`, `s_drainDiag`, `[npc-tick]`, `UM ↳ actions`) and close.
|
||||
**Severity:** MEDIUM (visual — remote combat / aggro)
|
||||
**Filed:** 2026-07-03 (user retail side-by-side during the R5-V2 visual gate)
|
||||
**Component:** animation, remote entity, combat
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AP-80 | **PlanFromVelocity survives for velocity-only NPC cycles** (M16): UpdatePosition-derived speed picks Ready/Walk/Run cycles for server-controlled creatures whose UMs never arrive (scripted-path NPCs); retail derives every cycle from motion messages through the motion tables (R4-V4 note; pre-existing mechanism, row added per the V4 plan) | `src/AcDream.Core/Physics/ServerControlledLocomotion.cs` (`PlanFromVelocity`); consumer `GameWindow.ApplyServerControlledVelocityCycle` | Some ACE entities move by position updates alone — without this, they slide in T-pose; constants (StopSpeed 0.2, RunThreshold 1.25) tuned against live ACE traffic | Cycle-pick thresholds are acdream inventions — a creature intended to walk fast may show run legs near the threshold | retire in R6 (root motion + full per-tick order) |
|
||||
| AP-81 | **Remote-DR gravity toggled via the Gravity STATE bit**: the jump handler sets `Body.State \|= Gravity` at VectorUpdate and both landing blocks clear it after `HitGround()`; retail keeps GRAVITY set for the object's whole life and gates gravity ACCELERATION on the Contact transient (`calc_acceleration`) (pre-existing K-fix9/K-fix15 mechanism, row added during #161 — which also fixed the ordering so `Motion.HitGround()`'s verbatim `state&0x400` gate runs BEFORE the clear) | `src/AcDream.App/Rendering/GameWindow.cs` (VectorUpdate jump handler + the two landing blocks) | The DR tick integrates gravity only for airborne remotes; the flag dance delivers exactly that without porting the full contact-gated `calc_acceleration` chain; the #161 ordering fix keeps the retail HitGround contract satisfied | Any NEW call into `Motion.HitGround`/`LeaveGround` placed after the clear silently no-ops on the gravity gate (the #161 leg-2 class); grounded remotes carry a non-retail state word (probes comparing state bits vs retail mislead) | `CPhysicsObj::calc_acceleration` (contact-gated); `set_on_walkable` 0x00511310; retire in R6 (contact-gated accel + persistent GRAVITY) |
|
||||
|
||||
## 4. Temporary stopgap (TS) — 36 rows (TS-37 is a retired-row historical note, not an active count)
|
||||
## 4. Temporary stopgap (TS) — 38 rows (TS-37 is a retired-row historical note, not an active count)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -221,6 +221,8 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| TS-35 | `PhysicsBody.IsFullyConstrained` is a stub property (default `false`, never set by any physics code), read by `jump_is_allowed`'s verbatim `IsFullyConstrained` gate (raw 305524-305525) | `src/AcDream.Core/Physics/PhysicsBody.cs` (`IsFullyConstrained`) | R3-W3 needed the read site to port `jump_is_allowed`'s full chain. **R5-V1 CORRECTED the mechanism** (the earlier "per-cell contact-plane / doorway-jamming" guess was WRONG): the write side is the **ConstraintManager server-position rubber-band leash** — armed by `SmartBox::HandleReceivedPosition` on every inbound server position, `IsFullyConstrained` = `max*0.9 < offset`. R5-V1 ported `ConstraintManager` (`src/AcDream.Core/Physics/Motion/ConstraintManager.cs`) but does NOT arm it (no acdream `SmartBox` + two x87 distance constants BN elided) — so this read stays false. Arming = issue #167 | A body retail would consider fully constrained (still rubber-banding toward a server position inside the tight leash) never refuses the jump (0x47) — a jump succeeds mid-rubber-band where retail blocks it. Low practical risk (the leash band is tight + short-lived) | `CPhysicsObj::IsFullyConstrained` 0x0050ec60 → `ConstraintManager::IsFullyConstrained` 0x005560d0; `jump_is_allowed` 0x005282b0; arming `SmartBox::HandleReceivedPosition` 0x00453fd0 (issue #167) |
|
||||
| TS-37 | RETIRED misattribution note (not a live divergence — kept here as the historical record R3-W3 closes): the S2a port had `contact_allows_move` (0x00528240) arm `StandingLongJump` as a side effect, explicitly flagged "PRE-EXISTING acdream side effect (not part of 0x00528240)". R3-W3 deletes that side effect; `ChargeJump` (0x005281c0) is now the ONLY arming site, matching retail exactly. No further action — recorded per the register's retire-in-same-commit rule | `src/AcDream.Core/Physics/MotionInterpreter.cs` (`contact_allows_move`, `ChargeJump`) | N/A — retired | N/A — retired | `CMotionInterp::charge_jump` 0x005281c0 @305448 |
|
||||
| TS-38 | `MotionInterpreter.Initted` defaults to `true` in both constructors, not retail's `false` — retail's `CMotionInterp` is never observed pre-`enter_default_state` (every real construction path calls it before exposing the interpreter); acdream's constructors are used directly by ~40 pre-existing tests and both App call sites as complete, immediately-usable objects with no separate "enter default state" step | `src/AcDream.Core/Physics/MotionInterpreter.cs` (`Initted` property + both constructors) | Defaulting `true` is the C# equivalent of "the constructor already did what `enter_default_state` would have done to this flag" — `EnterDefaultState()` remains available, verbatim, for the REST of retail's reset semantics (state defaults, sentinel enqueue, `LeaveGround` tail) when a caller wants them | None observed: no code path needs `apply_current_movement`/`ReportExhaustion` to no-op before an explicit `EnterDefaultState()` call, since nothing constructs a `MotionInterpreter` and defers initialization today. If a future caller DOES need staged construction (build now, `EnterDefaultState()` later), it must explicitly set `Initted = false` first | `CMotionInterp::enter_default_state` 0x00528c80 @306124 sets `initted = 1`; retire if/when construction is staged through `EnterDefaultState()` uniformly |
|
||||
| TS-41 | Grounded remote NPCs WITHOUT an armed moveto are body-driven by UP-synthesized server velocity (`HasServerVelocity` → the SERVERVEL per-tick leg: `Body.Velocity = ServerVelocity`, `MoveToManager.UseTime` SKIPPED, stale-decay stop via `ApplyServerControlledVelocityCycle(Zero)`); retail has no wire-velocity leg-driver anywhere — `MovementManager::UseTime` runs UNCONDITIONALLY per tick and between-UP translation comes from the motion state (`get_state_velocity`), UPs only hard-snap. **#170 residual fix narrowed this branch: an ARMED moveto (`MovementTypeState != Invalid`) now always takes the MOVETO leg** — the old arbitration starved the verbatim MoveToManager for exactly the duration of a server-side chase (UPs flowing → UseTime never ran → legs stayed Ready while the body glided = the #170 slide; live funnel 16 arms → 1 run install) | `src/AcDream.App/Rendering/GameWindow.cs` (`TickAnimations` grounded NPC branch, `moveToArmed` gate) | ACE moves some entities by position updates alone (scripted paths, missiles) with no UM/moveto stream — without a velocity fallback they freeze between UPs; entities WITH a moveto now get the retail drive | An entity class that carries BOTH wire velocity and an armed moveto with conflicting truths follows the moveto; UP hard-snaps bound the drift. Non-moveto entities keep the non-retail stale-stop heuristics (AP-80 thresholds) | `CPhysicsObj::UpdateObjectInternal` 0x005156b0 (`MovementManager::UseTime` call @0x00515998, unconditional); retire in R6 (full per-tick order) |
|
||||
| TS-42 | Per-tick DRAIN ORDER inverted vs retail: acdream's `TickAnimations` runs `HandleTargetting` → `MoveTo.UseTime` FIRST and the animation-completion drain (Sequencer.Advance → AnimDone hooks → `MotionTableManager.AnimationDone`/`UseTime` → `CMotionInterp.MotionDone` pops) LAST, so every motion-completion-gated decision (`BeginTurnToHeading`'s `motions_pending` wait) sees a queue that is one frame STALE — the unblock after a stop/swing lands one frame later than retail. Retail order (pinned from the named decomp this session): `UpdatePositionInternal` (CPartArray::Update + `process_hooks` @0x00512d3d — the drain) runs BEFORE `TargetManager::HandleTargetting` @0x00515989 → `MovementManager::UseTime` @0x00515998 → `CPartArray::HandleMovement` @0x005159a4 (zero-tick sweep) in `UpdateObjectInternal` | `src/AcDream.App/Rendering/GameWindow.cs` (`TickAnimations` per-entity phase order; the R2-Q4 comment already marks the placement "provisional until R6") | Bounded to exactly ONE frame (~16 ms) of extra latency per completion-gated event; every queue eventually drains identically (RemoteChaseEndToEndHarnessTests conformance) | Motion-completion-gated transitions (chase turn start, post-swing re-arm) systematically lag retail by one frame; under compound churn the lag can cost an extra retry cycle | `CPhysicsObj::UpdateObjectInternal` 0x005156b0 + `UpdatePositionInternal` 0x00512c30 (`process_hooks` @0x00512d3d); retire in R6 (retail UpdateObjectInternal order) |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,21 @@
|
|||
# Handoff — #170 creature chase renders as slide (PARTIAL FIX landed; residual = "sustain the run") — 2026-07-04
|
||||
|
||||
> **⚠ SUPERSEDED (2026-07-04, second session): the residual is FIXED — pending visual gate.**
|
||||
> The "Ready stop-node backlog drains a beat slower" framing below was DISPROVEN by a
|
||||
> full-stack offline harness (`tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs`)
|
||||
> plus corrected per-guid attribution of the launch-drainq.log evidence. The Core
|
||||
> drain/turn/run machinery is healthy; both handoff hypotheses ((a) tick counts,
|
||||
> (b) drain trigger rate) are moot. The real mechanism: the per-tick branch arbitration
|
||||
> sent any UP-receiving NPC down the SERVERVEL leg, which **skips `MoveToManager.UseTime`**
|
||||
> — the armed moveto was starved for the whole server-side chase (funnel: 16 arms →
|
||||
> 11 dispatched turns → 1 run install), legs stayed Ready while the body glided on
|
||||
> UP-synthesized velocity. Retail runs `MovementManager::UseTime` unconditionally
|
||||
> (`UpdateObjectInternal` 0x005156b0 @0x00515998). Fix: armed movetos always take the
|
||||
> MOVETO leg (GameWindow `TickAnimations`, `moveToArmed` gate; register TS-41 narrowed,
|
||||
> TS-42 added for the one-frame drain-order divergence). Current status + next steps
|
||||
> live in `docs/ISSUES.md` #170. This doc remains as the evidence record for the flood
|
||||
> fix (`427332ac`) and the cdb apparatus.
|
||||
|
||||
Fresh session picks up HERE for #170. Worktree `vigorous-joliot-f0c3ad`, branch
|
||||
`claude/vigorous-joliot-f0c3ad`. Tree CLEAN at **`427332ac`** (the partial fix +
|
||||
env-gated probes). This session root-caused #170 end-to-end with **live retail cdb
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue