docs(#182): crowd-collision investigation outcome + velocity-model rebuild design
The #182 CSphere port (96ae2740) failed its visual gate and introduced an
airborne "stuck in the falling animation" regression. A player-attributed retail
cdb trace (tools/cdb/retail-crowd-jump3.cdb) proved retail's LOCAL client fully
runs player-vs-creature collision (76 land_on_sphere, 188 COLLIDED, 130 SLID,
~78% OK, glides across) -- NOT server-authoritative (an earlier unfiltered
land_on_sphere=0 read was a false lead the attributed trace refuted).
acdream's same-repro capture: 50.9% OK, 22.4% stuck, 115 airborne-stuck. Root
divergence: retail CPhysicsObj::UpdateObjectInternal (0x005156b0, pc:283688) sets
cached_velocity = (resolved - old)/dt -- velocity from ACTUAL movement, so a
blocked jump collapses to ~0 -> gravity -> the player falls/glides. acdream
integrates velocity + reflects on collision (PlayerMovementController ~:1008-1069),
so the jump velocity (~18) persists against the creature -> hang.
Fix = verbatim rebuild of the per-frame player-physics loop (UpdateObjectInternal
chain), velocity model first, transition internals kept. Full design +
retail function inventory + the capture apparatus + retail target numbers:
docs/superpowers/specs/2026-07-07-player-physics-update-verbatim-rebuild-design.md.
Implementation deferred to a fresh session (user decision). Also files #183
(floating distant scenery, observed during testing). #182 stays as the base.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
96ae274081
commit
80881ed6f1
5 changed files with 305 additions and 1 deletions
|
|
@ -0,0 +1,167 @@
|
|||
# Design: verbatim rebuild of the per-frame player physics update loop
|
||||
|
||||
**Date:** 2026-07-07 · **Status:** DESIGN APPROVED (implementation deferred to a fresh session)
|
||||
**Driving issue:** #182 (player-vs-monster crowd collision) + the airborne "stuck in
|
||||
the falling animation" regression it exposed.
|
||||
**Brainstorm decisions (user):** verbatim rebuild (highest fidelity); scope = the
|
||||
per-frame update LOOP, keep everything already faithful; stage the velocity model
|
||||
first and measure; do the implementation in a NEW session.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem (from the user, live)
|
||||
|
||||
1. **Crowd jam** — packed in a group of monsters, the player gets stuck and can't
|
||||
push through / wiggle out. Retail lets you shove monsters aside and keep moving.
|
||||
2. **Airborne stuck** — jumping up into a crowd, retail lets you land/**glide across**
|
||||
the monster tops; acdream **hangs in the falling animation** unable to rise or fall.
|
||||
|
||||
The #182 CSphere-family port (committed `96ae2740`) was faithful but aimed a layer
|
||||
too low: it fixed the per-contact sphere *response*, did NOT fix the crowd feel, and
|
||||
**introduced the airborne-stuck regression** (before #182 the hand-rolled
|
||||
de-penetration shoved the player up onto monsters).
|
||||
|
||||
## 2. Evidence — the traces (the load-bearing part; do not re-derive)
|
||||
|
||||
### Retail, player-attributed cdb trace (`tools/cdb/retail-crowd-jump3.cdb`)
|
||||
Filtered to the local player's `CPhysicsObj` (captured from `set_local_velocity`),
|
||||
during a jump-into-a-crowd repro:
|
||||
|
||||
```
|
||||
pValidate=1482 pCOLLIDED=188 (12.7%) pSLID=130 (8.8%) pLand=76 → ~78% OK
|
||||
```
|
||||
|
||||
- The retail **local client fully runs player-vs-creature collision** — 76
|
||||
`CSphere::land_on_sphere` (airborne creature landings), 188 COLLIDED, 130 SLID —
|
||||
and still nets **~78% OK**. **NOT server-authoritative.** (An earlier v2 unfiltered
|
||||
read of `land_on_sphere=0` was a false lead — the attributed trace refuted it.)
|
||||
- The player **glides across / lands on** the crowd via local physics.
|
||||
|
||||
### acdream, `ACDREAM_CAPTURE_RESOLVE` JSONL capture (same repro, current #182 build)
|
||||
Of the player's move-intent resolves (2883 of 41832 total):
|
||||
|
||||
| | acdream | retail |
|
||||
|---|---|---|
|
||||
| OK (reached target) | **50.9%** | ~78% |
|
||||
| partial / slid | 26.7% | ~9% |
|
||||
| **stuck (reverted, no advance)** | **22.4%** | ~13% |
|
||||
| airborne-stuck | **115 frames** | (glides off) |
|
||||
|
||||
acdream jams ~2× harder, and the 115 airborne-stuck frames ARE the falling-animation
|
||||
bug. In those frames: velocity `(0,0,~18)` (a jump), a **near-horizontal creature
|
||||
collision normal** (e.g. `(-0.96,-0.25,-0.15)`), the transition reverts (no advance),
|
||||
and the **sliding normal flips frame-to-frame** — the #137 anti-parallel-absorb wedge.
|
||||
|
||||
## 3. Root divergence (verified against decomp + capture)
|
||||
|
||||
**Retail** `CPhysicsObj::UpdateObjectInternal` (0x005156b0, pc:283611) recomputes the
|
||||
player's velocity from the ACTUAL collided movement every frame:
|
||||
|
||||
```
|
||||
newFrame = UpdatePositionInternal(this, dt) // integrate velocity + gravity
|
||||
transition = CPhysicsObj::transition(this, m_position, newFrame, 0) // collision sweep
|
||||
if transition == null: set_frame(newFrame); cached_velocity = 0
|
||||
else:
|
||||
offset = get_offset(m_position, transition.sphere_path.curr_pos)
|
||||
cached_velocity = offset / dt // ← VELOCITY = ACTUAL MOVEMENT / dt (0x005158cb-005158ff)
|
||||
SetPositionInternal(transition)
|
||||
```
|
||||
|
||||
So when the player is **blocked** (resolved ≈ old position), `cached_velocity → ~0`,
|
||||
gravity resumes next frame, and the player **falls / settles / glides** off the
|
||||
obstacle. The collision naturally bleeds the velocity — there is no reflection step.
|
||||
|
||||
**acdream** uses a different model: `PhysicsBody` integrates velocity/gravity, then
|
||||
`PlayerMovementController` (`~:1008-1069`) **reflects** the into-wall component on
|
||||
collision (`Velocity = v + n·k`) and clamps Z on landing. For a straight-up jump into
|
||||
a creature whose collision normal is horizontal, the reflection barely touches +Z, so
|
||||
the jump velocity **persists** — the player keeps shoving up into the creature and
|
||||
hangs. This is the airborne-stuck, and the same "velocity doesn't bleed on block"
|
||||
shape drives the general 2× jam.
|
||||
|
||||
**This velocity model is the centerpiece of the fix.**
|
||||
|
||||
## 4. Design — verbatim rebuild of the per-frame loop
|
||||
|
||||
Port retail's `UpdateObjectInternal` chain verbatim into a single owner of the
|
||||
per-frame player physics. **Reuse the already-faithful transition internals** as the
|
||||
collision primitive — do not touch them.
|
||||
|
||||
### 4.1 Retail functions to port (chain of `UpdateObjectInternal`)
|
||||
| Function | Address | Role |
|
||||
|---|---|---|
|
||||
| `CPhysicsObj::UpdateObjectInternal` | 0x005156b0 | per-frame entry: integrate → transition → commit → velocity |
|
||||
| `CPhysicsObj::UpdatePositionInternal` | (grep) | integrate velocity + gravity + forces → candidate frame |
|
||||
| `CPhysicsObj::calc_acceleration` | 0x00510950 | gravity / friction / buoyancy |
|
||||
| `CPhysicsObj::handle_all_collisions` | 0x00514780 | retail's collision velocity response (verify role vs the cached_velocity model) |
|
||||
| `CPhysicsObj::SetPositionInternal` | 0x00515330 / 0x00515bd0 / 0x00516040 (3 overloads) | commit the transition's resolved position |
|
||||
| `CPhysicsObj::transition` | (grep) | collision sweep — **maps to acdream `ResolveWithTransition`, reused as-is** |
|
||||
|
||||
### 4.2 In scope (rebuilt verbatim)
|
||||
The per-frame player physics loop: velocity + gravity integration
|
||||
(`UpdatePositionInternal`/`calc_acceleration`), the transition orchestration
|
||||
(call → commit), the **velocity-from-movement recompute** (`cached_velocity =
|
||||
(resolved − old)/dt`), and `handle_all_collisions`.
|
||||
|
||||
### 4.3 OUT of scope (kept — already faithful, per the user "keep everything faithful")
|
||||
The transition INTERNALS: `ResolveWithTransition` and everything under it — BSPQuery,
|
||||
the CSphere/CylSphere collision families (incl. the #182 port), cell membership,
|
||||
terrain, streaming. Retail's `transition` calls these primitives and so does ours.
|
||||
|
||||
### 4.4 Architecture
|
||||
A new `PhysicsObjUpdater` (or a `PhysicsBody.UpdateObjectInternal(dt)` method) owns
|
||||
the retail chain verbatim. It **absorbs**: `PhysicsBody`'s ad-hoc gravity/friction
|
||||
integration AND `PlayerMovementController`'s post-resolve bounce/reflect/clamp block
|
||||
(`~:1008-1069`). `PlayerMovementController` shrinks to: input → velocity/jump intent →
|
||||
hand off. The retail velocity-from-movement model **replaces** reflect-and-clamp
|
||||
wholesale. Register a divergence-register row retiring the reflect model + any
|
||||
adaptation rows the port introduces.
|
||||
|
||||
### 4.5 Staging (velocity model first, measured)
|
||||
- **Slice 1:** port `UpdatePositionInternal` + `calc_acceleration` + the
|
||||
`cached_velocity = (resolved−old)/dt` recompute, replacing the integrate+reflect
|
||||
path. A/B **against the crowd capture**: require the airborne-stuck count → 0 and OK%
|
||||
to climb toward retail's ~78%. Gate (suites + a visual pass on ordinary
|
||||
jump/walk/fall) before continuing.
|
||||
- **Slice 2:** `handle_all_collisions` verbatim (once Slice 1 shows what residual the
|
||||
velocity model leaves) + any remaining `UpdateObjectInternal` orchestration.
|
||||
- **Slice 3+:** close the residual ground-jam gap if the velocity model alone doesn't
|
||||
reach ~78% OK; re-measure each slice against the capture.
|
||||
|
||||
## 5. Validation
|
||||
- **Apparatus (built this session, reuse):** `ACDREAM_CAPTURE_RESOLVE=<path>` JSONL of
|
||||
every player resolve; the `py` histogram (OK / partial / stuck / airborne-stuck; see
|
||||
§2). Retail target: **~78% OK, 12.7% COLLIDED, 8.8% SLID, 0 airborne-stuck**. Retail
|
||||
cdb scripts: `tools/cdb/retail-crowd-jump{2,3}.cdb` (v3 = player-attributed; safe
|
||||
auto-detach via `validate_transition` terminal + top-level `qd`).
|
||||
- **Conformance tests:** velocity-from-movement (blocked → velocity → ~0); jump +
|
||||
gravity integration; the `UpdateObjectInternal` sequence; a crowd replay.
|
||||
- **Suites stay green:** Core 2603 / App 741 (no regression to walking/stairs/slopes/
|
||||
walls/streaming — the transition internals are untouched).
|
||||
- **Visual gates:** (a) crowd glide/land like retail (the #182 symptom); (b) a
|
||||
regression pass on normal locomotion — flat walking, slopes, stairs, jumping, falling,
|
||||
wall-slide.
|
||||
|
||||
## 6. Risk
|
||||
Highest-risk option: it replaces the core of every jump/fall/step. Mitigation: the
|
||||
scope boundary (transition internals untouched), the measured slicing (Slice 1 behind
|
||||
an A/B compare), and heavy gating. If Slice 1 regresses ordinary locomotion, bisect
|
||||
within the velocity model before proceeding.
|
||||
|
||||
## 7. Open items for the new session (verify during writing-plans)
|
||||
1. Read `UpdatePositionInternal`, `calc_acceleration` (0x00510950), and
|
||||
`handle_all_collisions` (0x00514780) fully — confirm the integration + the role of
|
||||
`handle_all_collisions` vs the cached_velocity model (does retail ALSO reflect, or
|
||||
is velocity purely movement-derived?).
|
||||
2. Confirm which `SetPositionInternal` overload the player path uses (3 exist).
|
||||
3. Decide whether the general ground-jam (22% stuck vs retail 13%) is fully explained
|
||||
by the velocity model or needs a second divergence (de-penetration / sliding-normal
|
||||
provenance — the #137/TS-45 wedge family). Measure after Slice 1.
|
||||
4. #182 stays as the base (user decision). NOTE: until this rebuild lands, the codebase
|
||||
carries the airborne-stuck regression #182 introduced.
|
||||
|
||||
## 8. Pointers
|
||||
- Physics/collision SSOT: `claude-memory/project_physics_collision_digest.md` (updated
|
||||
with the #182 + this-investigation banner).
|
||||
- #182 issue: `docs/ISSUES.md`. CSphere pseudocode: `docs/research/2026-07-07-csphere-collision-family-pseudocode.md`.
|
||||
- Retail debugger toolchain + the safe-detach recipe: `memory/project_retail_debugger.md` + CLAUDE.md.
|
||||
Loading…
Add table
Add a link
Reference in a new issue