fix(runtime): align portal and movement presentation
Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once. User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
e95f55f25b
commit
124e046976
30 changed files with 1362 additions and 348 deletions
190
docs/ISSUES.md
190
docs/ISSUES.md
|
|
@ -44,6 +44,78 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #220 — Local player glides before locomotion animation starts
|
||||
|
||||
**Status:** DONE 2026-07-17 — user-confirmed connected visual gate
|
||||
**Severity:** HIGH
|
||||
**Filed:** 2026-07-17
|
||||
**Component:** local movement / animation / physics
|
||||
|
||||
**Description:** A very short W tap moved the acdream character before the
|
||||
Ready→Walk/Run animation visibly started, producing a brief foot-slide.
|
||||
|
||||
**Root cause / status:** The grounded local controller called
|
||||
`CMotionInterp::get_state_velocity` and installed the full 3.12–4.0 m/s body
|
||||
velocity on the input edge. Retail instead advances `CPartArray` first and
|
||||
uses the exact root displacement authored by the current transition/cycle;
|
||||
`PositionManager::adjust_offset` then composes into that same Frame before the
|
||||
collision sweep. The local player now advances its owning `AnimationSequencer`
|
||||
after input dispatch, accumulates the emitted root displacement to the 30 Hz
|
||||
physics quantum, and passes it through the existing PositionManager/collision
|
||||
path. The prepared part pose is reused by the presentation pass so animation
|
||||
time and hooks advance exactly once.
|
||||
|
||||
**Files:** `src/AcDream.App/Input/PlayerMovementController.cs`;
|
||||
`src/AcDream.App/Rendering/GameWindow.cs`;
|
||||
`tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs`;
|
||||
`tests/AcDream.Core.Tests/Physics/HumanoidMotionTableRootMotionTests.cs`.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-17-local-player-root-motion-start-pseudocode.md`.
|
||||
|
||||
**Acceptance:** From rest, tap W briefly several times. The body must not move
|
||||
before the Ready→Walk/Run transition produces movement. Holding W must still
|
||||
reach normal continuous speed; release, backward/strafe, and jump remain
|
||||
correct.
|
||||
Passed 2026-07-17: “Good! works.”
|
||||
|
||||
---
|
||||
|
||||
## #219 — Outbound movement dropped the player's combat stance
|
||||
|
||||
**Status:** DONE 2026-07-17 — user-confirmed from a retail observer
|
||||
**Severity:** HIGH
|
||||
**Filed:** 2026-07-17
|
||||
**Component:** movement wire / combat stance / remote animation
|
||||
|
||||
**Description:** acdream looked correct locally while moving in combat mode,
|
||||
but a retail observer saw the character leave combat stance and locomote in
|
||||
NonCombat.
|
||||
|
||||
**Root cause / status:** Retail `CommandInterpreter::SendMovementEvent` passes
|
||||
the canonical `MovementManager::RawMotionState` directly into
|
||||
`MoveToStatePack`. acdream instead projected the raw axes through
|
||||
`MovementResult` and rebuilt a packet state without `CurrentStyle`, leaving its
|
||||
retail default `0x8000003D` NonCombat. `RawMotionStatePacker` therefore omitted
|
||||
the style bit; ACE relayed no combat stance, and retail's unpack correctly
|
||||
defaulted the observer to NonCombat. `MovementResult` now carries the canonical
|
||||
`MotionInterpreter.RawState.CurrentStyle`, and the outbound builder writes it
|
||||
without inferring from CombatMode or equipment.
|
||||
|
||||
**Files:** `src/AcDream.App/Input/PlayerMovementController.cs`;
|
||||
`src/AcDream.App/Input/LocalPlayerOutboundController.cs`;
|
||||
`tests/AcDream.App.Tests/Input/LocalPlayerOutboundCombatStyleTests.cs`.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-17-combat-movement-outbound-style-pseudocode.md`.
|
||||
|
||||
**Acceptance:** With a melee, bow/crossbow, or magic stance active, move the
|
||||
acdream player while observing from retail. The observer retains the same
|
||||
combat stance and uses its combat locomotion instead of switching to peace.
|
||||
Passed 2026-07-17: “Yes works.”
|
||||
|
||||
---
|
||||
|
||||
## #218 — Portal silhouette pose, destination reveal, and indoor observer snap
|
||||
|
||||
**Status:** IN-PROGRESS — root fixes implemented 2026-07-16; pending connected visual gate
|
||||
|
|
@ -93,7 +165,10 @@ removed by owner before prefix cleanup, including footprints flooded across a
|
|||
landblock seam, while server-live/dynamic registrations remain refloodable.
|
||||
Full unload uses the same owner rule; surviving owners seeded across the seam
|
||||
track withdrawn prefixes so a later reload restores their missing collision
|
||||
rows.
|
||||
rows. Destination reveal now also keeps sky and landscape on the same active
|
||||
SmartBox projection, matching `SmartBox::RenderNormalMode`/`GameSky::Draw`;
|
||||
the old fixed 60-degree sky camera exposed the clear/fog background during the
|
||||
nearly 180-degree exit warp.
|
||||
|
||||
**Files:** `src/AcDream.Core/Physics/AnimationSequencer.cs`;
|
||||
`src/AcDream.App/Rendering/GameWindow.cs`;
|
||||
|
|
@ -101,6 +176,7 @@ rows.
|
|||
`src/AcDream.App/Streaming/GpuWorldState.cs`;
|
||||
`src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs`;
|
||||
`src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs`;
|
||||
`src/AcDream.App/Rendering/Sky/SkyProjection.cs`;
|
||||
`src/AcDream.Core/World/ProceduralSceneryIdAllocator.cs`;
|
||||
`src/AcDream.Core/Physics/PhysicsBody.cs`;
|
||||
`src/AcDream.App/Input/PlayerMovementController.cs`.
|
||||
|
|
@ -4802,85 +4878,49 @@ the local terrain normal, not the actor's facing.
|
|||
|
||||
## #41 — Residual sub-decimeter blips on observed player remotes (M3 baseline)
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** LOW (within retail's own DesiredDistance / MinDistance tolerances; visible only on close inspection)
|
||||
**Status:** FIX IMPLEMENTED 2026-07-17 — pending acdream-observer visual gate
|
||||
**Severity:** MEDIUM
|
||||
**Filed:** 2026-05-05
|
||||
**Component:** physics / motion / animation (per-tick remote prediction)
|
||||
**Phase:** L.2 (Movement & Collision Conformance) — inbound-motion fidelity sub-piece. Blocked on cdb-trace of `CSequence::velocity` for Humanoid running cycle, then porting `add_motion @ 0x005224b0`'s `style_speed × MotionData.velocity` chain.
|
||||
**Phase:** L.2 (Movement & Collision Conformance) — inbound-motion fidelity sub-piece.
|
||||
|
||||
**Description:** With the L.3 M3 path live (queue catch-up + animation
|
||||
root motion fallback), observed player remotes chase server position
|
||||
smoothly with NO staircase on slopes and NO per-UP rubber-band. However
|
||||
small position blips remain — sub-decimeter amplitude, periodic with
|
||||
the server's UP cadence (~1 Hz). User report 2026-05-05: "I get very
|
||||
small blips now. Running works, walking works, strafing works."
|
||||
**Description:** Observed characters walked forward, blipped backward, then
|
||||
continued, periodic with the server's UpdatePosition cadence. The earlier
|
||||
2026-05-05 report called the residue sub-decimeter; on 2026-07-17 the user
|
||||
reported the same mechanism as plainly visible repeated rollback.
|
||||
|
||||
The blips fall well within retail's own tolerances:
|
||||
**Root cause / fix:** Retail `CPhysicsObj::UpdatePositionInternal @ 0x00512C30`
|
||||
advances `CPartArray::Update` into a complete local root-motion Frame, then
|
||||
lets `PositionManager::adjust_offset` replace that Frame with interpolation
|
||||
catch-up. `add_motion @ 0x005224B0` writes only literal
|
||||
`MotionData.Velocity × speed` into CSequence. acdream instead synthesized
|
||||
Walk/Run constants into `AnimationSequencer.CurrentVelocity` and reconstructed
|
||||
a delta after the queue emptied. The installed Humanoid MotionTable proves
|
||||
both Walk and Run carry zero MotionData velocity, so acdream ran past the last
|
||||
server waypoint; the next UpdatePosition pulled it backward.
|
||||
|
||||
- `DesiredDistance` (queue head reach radius) = 0.05 m
|
||||
- `MinDistanceToReachPosition` (primary stall threshold) = 0.20 m
|
||||
|
||||
So they are NOT a stall trigger and NOT a correctness bug. They're a
|
||||
visible artifact of the velocity-synthesis residual: anim root motion
|
||||
(`AnimationSequencer.CurrentVelocity = RunAnimSpeed × adjustedSpeed`)
|
||||
slightly overshoots server pace between UPs, then queue catch-up walks
|
||||
the body back toward the server position on the next UP — a small
|
||||
rubber-band that's smaller than M2's pre-fix version but still
|
||||
perceptible.
|
||||
|
||||
**Root cause hypothesis (untested):**
|
||||
|
||||
The L.3 handoff explicitly flagged this. From `06-acdream-audit.md` § 9
|
||||
and `05-position-manager-and-partarray.md` § 7:
|
||||
|
||||
> Our `CurrentVelocity` carries only the steady-state component of the
|
||||
> cycle's intent; the per-frame stride wobble is gone… For Humanoid
|
||||
> the dat ships `MotionData.Velocity = 0` so the multiply is a no-op
|
||||
> anyway — but the synth uses `RunAnimSpeed × adjustedSpeed` directly.
|
||||
|
||||
ACE's wire `ForwardSpeed` for a running player is the **server runRate**
|
||||
(~2.94 for skill 200), not a unit multiplier. Our synth multiplies
|
||||
`RunAnimSpeed` (4.0) by `adjustedSpeed` (~2.94) = ~11.76 m/s, which
|
||||
the queue catch-up clamps via `min(catchUp × dt, dist)` but the anim
|
||||
fallback applies in full when the queue is idle. If the actual
|
||||
server-broadcast pace is closer to 4.0 m/s (RunAnimSpeed alone, with
|
||||
runRate as a *frame-rate* multiplier rather than a velocity scalar),
|
||||
our fallback overshoots by ~3× and the queue walks it back every UP.
|
||||
|
||||
Per the handoff: **don't normalize at the wire boundary** (prior
|
||||
session tried this, called it a hack). The right fix is porting
|
||||
retail's actual behavior in `add_motion @ 0x005224b0` and
|
||||
`apply_run_to_command` to determine the correct `CSequence::velocity`
|
||||
magnitude.
|
||||
Remote animation now advances before remote physics into the already-ported
|
||||
CSequence root-motion Frame. `RemotePhysicsUpdater` consumes that exact local
|
||||
delta, and `RemoteMotionCombiner` applies the retail interpolation-replaces-
|
||||
root-motion rule. Command-derived locomotion synthesis was removed from
|
||||
CSequence; the local interpreted body path retains retail
|
||||
`CMotionInterp::get_state_velocity` through its existing zero-data fallback.
|
||||
|
||||
**Files:**
|
||||
|
||||
- `src/AcDream.Core/Physics/AnimationSequencer.cs` — `CurrentVelocity`
|
||||
synthesis at L614–679 (RunAnimSpeed=4.0, WalkAnimSpeed=3.12,
|
||||
SidestepAnimSpeed=1.25 × adjustedSpeed)
|
||||
- `src/AcDream.Core/Physics/PositionManager.cs` — `ComputeOffset`
|
||||
applies `seqVel × dt × orientation` as fallback when queue is idle
|
||||
- `src/AcDream.Core/Physics/AnimationSequencer.cs`
|
||||
- `src/AcDream.Core/Physics/RemoteMotionCombiner.cs`
|
||||
- `src/AcDream.App/Physics/RemotePhysicsUpdater.cs`
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs`
|
||||
- `tests/AcDream.Core.Tests/Physics/HumanoidMotionTableRootMotionTests.cs`
|
||||
|
||||
**Research:**
|
||||
|
||||
- `docs/research/2026-05-04-l3-port/05-position-manager-and-partarray.md` § 5–7
|
||||
- `docs/research/2026-05-04-l3-port/06-acdream-audit.md` § 9 (AnimationSequencer)
|
||||
- `docs/research/named-retail/acclient_2013_pseudo_c.txt` line 298437
|
||||
(`add_motion @ 0x005224b0`) — `CSequence::velocity = style_speed × MotionData.velocity`
|
||||
|
||||
**Fix path (research first, then port):**
|
||||
|
||||
1. cdb-trace retail to capture `CSequence::velocity` and
|
||||
`MotionData::velocity` for a Humanoid running cycle. Compare against
|
||||
our synth (4.0 × 2.94 = 11.76 m/s) to determine the actual retail
|
||||
magnitude.
|
||||
2. Port `add_motion`'s `style_speed × MotionData.velocity` chain
|
||||
verbatim. For Humanoid where `MotionData.Velocity = 0`, port the
|
||||
fallback retail uses (likely a separate code path through
|
||||
`apply_run_to_command` that derives velocity from the cycle's
|
||||
framerate, not a constant).
|
||||
3. Remove the `RunAnimSpeed × adjustedSpeed` synth in
|
||||
`AnimationSequencer.SetCycle`.
|
||||
- `docs/research/2026-07-17-remote-root-motion-reconciliation-pseudocode.md`
|
||||
- `docs/research/2026-05-02-remote-entity-motion/resolved-via-cdb.md`
|
||||
- named retail `CPhysicsObj::UpdatePositionInternal @ 0x00512C30`,
|
||||
`CPartArray::Update @ 0x00517DB0`, `add_motion @ 0x005224B0`, and
|
||||
`InterpolationManager::adjust_offset @ 0x00555D30`
|
||||
|
||||
**Acceptance:**
|
||||
|
||||
|
|
@ -4888,8 +4928,6 @@ magnitude.
|
|||
- Side-by-side acdream-as-observer vs retail-as-observer of the same
|
||||
server-controlled toon: indistinguishable body trajectory.
|
||||
|
||||
**2026-07-09 note (user):** "semi fixed, needs polish" — stays open, downgrade to polish-tier if a fresher look confirms the core bug is gone.
|
||||
|
||||
---
|
||||
|
||||
## #40 — [DONE 2026-05-05 · 40d88b9] ACDREAM_INTERP_MANAGER=1 env-var path regressed (staircase + blips)
|
||||
|
|
@ -7264,7 +7302,7 @@ The burden bar now reads the server's wire `EncumbranceVal` (PropertyInt 5) inst
|
|||
## #113 — Phantom staircase / holistic building-render port
|
||||
|
||||
**Status:** DONE (2026-07-09, user-confirmed via memory during an issues-triage pass — not independently re-verified this session).
|
||||
The Holtburg meeting-hall's walkable stair-ramp painted across the exterior wall because the PView shell-clip pass computed correct clip regions but never enabled `GL_CLIP_DISTANCE` — fixed by `927fd8f`. The bug reopened 2026-06-11 when a second root cause surfaced (dictionary-referenced-but-undrawn BSP polys drawn that retail never draws); per the user's mandate this was folded into the holistic building-render port effort (#113/#114/#108/#109/#99, charter at `docs/research/2026-06-11-building-render-holistic-port-handoff.md`), which shipped as part of the broader render-pipeline redesign (Option A) closed 2026-06-12.
|
||||
The Holtburg meeting-hall's walkable stair-ramp painted across the exterior wall because the PView shell-clip pass computed correct clip regions but never enabled `GL_CLIP_DISTANCE` — fixed by `927fd8f`. The bug reopened 2026-06-11 when a suspected second root cause surfaced (dictionary-referenced-but-undrawn BSP polys); per the user's mandate this was folded into the holistic building-render port effort (#113/#114/#108/#109/#99, charter at `docs/research/2026-06-11-building-render-holistic-port-handoff.md`), which shipped as part of the broader render-pipeline redesign (Option A) closed 2026-06-12. A 2026-07-16 named-retail audit later disproved the proposed global DrawingBSP polygon filter: `CGfxObj::InitLoad`/`D3DPolyRender::ConstructMesh` consume the complete polygon array, and the alleged shell orphans are portal references omitted by the old diagnostic collector. See retired divergence TS-20; do not retry `e46d3d9`'s filter, which made doors disappear.
|
||||
|
||||
## #111 — ACE-mutated indoor restores: transparent interior / wrong placement at login — [DONE 2026-06-10 · 5f1eb7c + 5706e0e + 2735695]
|
||||
|
||||
|
|
@ -7841,18 +7879,18 @@ formula.
|
|||
**Resolution:** Grounded player remotes showed a ~5 Hz Z staircase when
|
||||
running up/down hills. `PositionManager.ComputeOffset` has two modes:
|
||||
queue-active (3D direction toward server's broadcast position, Z
|
||||
follows naturally) and queue-empty / head-reached (`seqVel × dt`
|
||||
rotated into world). Every locomotion cycle bakes Z=0 in body-local,
|
||||
follows naturally) and queue-empty / head-reached (the CSequence
|
||||
root-motion delta rotated into world). Every locomotion cycle bakes Z=0 in body-local,
|
||||
so the world result has Z=0 too. With server UPs at ~5 Hz and
|
||||
catchUpSpeed = 2× maxSpeed, body chases each waypoint in ~100ms (Z
|
||||
ramps), then sits in seqVel-only mode for ~100ms (Z flat) until the
|
||||
ramps), then sits in root-motion-only mode for ~100ms (Z flat) until the
|
||||
next UP. Visible 5 Hz staircase.
|
||||
|
||||
Fix mirrors retail's `CTransition::adjust_offset` contact-plane
|
||||
projection (named-retail acclient_2013_pseudo_c.txt:272296-272346),
|
||||
applied at the queue-empty boundary instead of inside the sweep.
|
||||
`ComputeOffset` gains an optional `Vector3? terrainNormal`; when
|
||||
the seqVel fallback runs and the supplied normal is non-trivial,
|
||||
the root-motion fallback runs and the supplied normal is non-trivial,
|
||||
`rootMotionWorld -= N × dot(rootMotionWorld, N)`. XY motion gains a
|
||||
Z component proportional to slope × forward speed; body Z follows the
|
||||
terrain mesh between UPs. No-op on flat ground (N ≈ +Z, dot ≈ 0) so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue