feat(physics): #32 L.5 30Hz physics tick + retail debugger toolchain (#35) + Phase 3 retail-faithful kill_velocity
Three intertwined changes from a single investigation session driven by
attaching cdb to a live retail acclient.exe (v11.4186, Sept 2013 EoR
build) and tracing what retail actually DOES on the steep-roof wedge
scenario the user reported in acdream.
═══════════════════════════════════════════════════════════
1. L.5 — physics-tick MinQuantum gate (PlayerMovementController)
═══════════════════════════════════════════════════════════
Retail's CPhysicsObj::update_object subdivides per-frame dt into 1/30 s
sized integration steps and SKIPS entirely when accumulated dt is below
MinQuantum. Live trace evidence:
update_object = 40,960 calls
UpdatePhysicsInternal = 25,087 calls (61%)
i.e., 39% of update_object calls return early via the MinQuantum gate.
Retail's effective physics tick rate is 30Hz even at 60+ Hz render.
acdream's PlayerMovementController bypassed the existing PhysicsBody.
update_object and called UpdatePhysicsInternal(dt) directly each render
frame, which compressed bounce-energy / gravity-tangent accumulation
into half the time and amplified our steep-roof wedge dynamics.
Fix: add `_physicsAccum` accumulator. Integrate only when accumulated
dt ≥ MinQuantum (clamped to MaxQuantum to bound stale-frame jumps).
HugeQuantum drops accumulated time to discard truly stale frames
(debugger break, GC pause). Render still runs at full rate; only the
physics step is gated.
═══════════════════════════════════════════════════════════
2. Phase 3 reset retail-faithful kill_velocity (TransitionTypes)
═══════════════════════════════════════════════════════════
Retail's reset path (acclient_2013_pseudo_c.txt:273231-273239) gates
kill_velocity on `last_known_contact_plane_valid`:
if (last_known_valid == 0) {
set_collision_normal(step_up_normal); return COLLIDED;
}
kill_velocity(this);
last_known_valid = 0;
return COLLIDED;
Earlier in this session I deviated to "unconditional kill_velocity" as
a hypothesis-driven wedge fix. The live trace then showed the
deviation CAUSED a different wedge by zeroing V every frame, leaving
the body with no tangent momentum to escape (V = (0,0,0) for 169
consecutive frames while position pre/resolved frozen). The retail-
faithful gate is restored.
Note: the gate rarely fires in normal airborne play because our L.2.4
proximity guard clears last_known_valid soon after the body separates
from its remembered floor. Live retail trace also showed
kill_velocity = 0 hits over an entire play session — same behavior. So
acdream's kill_velocity is correct as ported now.
The supporting ObjectInfo.VelocityKilled flag + StopVelocity wiring +
PhysicsEngine.ResolveWithTransition consumer that actually zeros
body.Velocity when the flag is set — these were a no-op stub before
this session and are now correctly wired. Retail anchor:
OBJECTINFO::kill_velocity → CPhysicsObj::set_velocity({0,0,0}, 0) at
acclient_2013_pseudo_c.txt:274467-274475.
═══════════════════════════════════════════════════════════
3. Retail debugger toolchain (#35)
═══════════════════════════════════════════════════════════
When the question is "what does retail actually DO at runtime?" — not
"what does retail's code SAY" — the decomp at docs/research/named-retail/
is invaluable but doesn't capture state interactions across frames.
This commit ships infrastructure to attach Windows' cdb.exe to a live
retail acclient.exe with full PDB symbols and capture state at any
breakpoint.
- tools/pdb-extract/check_exe_pdb.py — reads any PE's CodeView entry
and reports MATCH / MISMATCH against refs/acclient.pdb's GUID.
Always run before attaching cdb. The matching v11.4186 build's
GUID is 9e847e2f-777c-4bd9-886c-22256bb87f32.
- tools/pdb-extract/dump_pdb_info.py — dumps a PDB's expected
build timestamp + GUID + age. Used to figure out which acclient.exe
build pairs with our PDB.
CLAUDE.md gets a Step -1 in the development workflow ("ATTACH cdb
TO RETAIL when behavior is the question, not code") and a full
"Retail debugger toolchain" section with the workflow, sample .cdb
script structure, and watchouts (PDB names use snake_case for some
classes / PascalCase for CPhysicsObj; ; is cdb's command separator;
killing cdb kills the debuggee; high-hit-rate breakpoints lag the game).
memory/project_retail_debugger.md captures the workflow + key findings
so future sessions inherit the toolchain by reading project memory.
═══════════════════════════════════════════════════════════
4. BSPQuery Path 6 slide-tangent restored (b1af56e behavior)
═══════════════════════════════════════════════════════════
After this session's retail-strict experiments showed that retail-
faithful Path 6 (SetCollide + Phase 3 reset chain) produces a
"lands on roof in falling animation, can't slide off" half-state in
acdream — because our acdream port of step_up_slide / cliff_slide is
incomplete for grounded-on-steep movement — the L.4 slide-tangent
deviation from commit b1af56e is restored as the pragmatic ship state.
The deviation: when an airborne sphere hits a polygon whose normal Z
is below FloorZ (≈ 0.6642, slope > ~49°), project the move along the
steep face to remove the into-wall displacement, set CollisionNormal +
SlidingNormal, return Slid. Body never gets ContactPlane on the steep
poly, never gets the half-state, slides off the slope under gravity's
tangent contribution.
Retail-strict requires the deeper step_up_slide / cliff_slide audit
(filed under #32). Until that lands, slide-tangent is the right
deviation — produces user-acceptable "slide off the roof" behavior.
═══════════════════════════════════════════════════════════
Test status: 833/833 green.
Refs:
acclient_2013_pseudo_c.txt:283950 (CPhysicsObj::update_object)
acclient_2013_pseudo_c.txt:273231-273239 (Phase 3 reset path)
acclient_2013_pseudo_c.txt:274467-274475 (OBJECTINFO::kill_velocity)
acclient_2013_pseudo_c.txt:323783-323821 (BSPTREE::find_collisions Path 6)
Closes #35. Updates #32 with L.4/L.5 status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b1af56eb19
commit
235de3322a
8 changed files with 624 additions and 82 deletions
|
|
@ -196,11 +196,44 @@ before `SPHEREPATH::precipice_slide`; edge-slide `Slid` / `Adjusted` results
|
|||
now feed the `TransitionalInsert` retry loop instead of being reverted by outer
|
||||
validation, and a synthetic diagonal terrain-boundary test covers tangent
|
||||
motion. `ACDREAM_DUMP_EDGE_SLIDE=1` now reports whether a failed step-down had
|
||||
polygon context. Remaining gaps: live visual confirmation of the retry-loop
|
||||
fix, real-DAT building-edge fixtures, fuller `cliff_slide` coverage, and
|
||||
`NegPolyHit` dispatch. Named retail anchors include `CTransition::edge_slide`,
|
||||
`CTransition::cliff_slide`, `SPHEREPATH::precipice_slide`, and
|
||||
`SPHEREPATH::step_up_slide`.
|
||||
polygon context.
|
||||
|
||||
**L.4/L.5 update 2026-04-30:** A retail debugger trace (cdb attached to
|
||||
v11.4186 acclient.exe — see #35) confirmed that retail does NOT wedge
|
||||
on the steep-roof scenario that produces the wedge in our acdream port.
|
||||
Three concrete findings:
|
||||
1. Retail's `OBJECTINFO::kill_velocity` rarely fires in normal play —
|
||||
gated on `last_known_contact_plane_valid`, which our L.2.4 proximity
|
||||
guard tends to clear before steep-poly hits land. Retail trace: 0
|
||||
kill_velocity hits across 40,960 update_object calls. Our Phase 3
|
||||
reset path now matches retail's gate (only kills when valid).
|
||||
2. Retail integrates physics at 30Hz (`MinQuantum = 1/30 s`); render is
|
||||
60+ Hz. UpdatePhysicsInternal/update_object ratio = 0.61. We
|
||||
ported this gate as L.5 in `PlayerMovementController` via
|
||||
`_physicsAccum`. Render still runs at 60+ Hz; only the physics
|
||||
integration step is 30Hz.
|
||||
3. The remaining wedge cause — body's pre-position drifts to the
|
||||
polygon's tangent and gravity's tangent component into surface
|
||||
produces a stable retain-collide-revert loop — is a downstream
|
||||
consequence of retail's grounded-on-steep escape chain
|
||||
(`step_sphere_up` → `step_up_slide` → `cliff_slide`) being
|
||||
incompletely ported. Live test confirmed retail-strict Path 6
|
||||
produces "lands on roof in falling animation, can't slide off"
|
||||
half-state because that chain doesn't produce smooth descent.
|
||||
|
||||
**Pragmatic ship-state:** BSPQuery Path 6 keeps the L.4 slide-tangent
|
||||
deviation (project-along-steep-face-and-return-Slid) for steep-poly
|
||||
airborne hits. It produces user-acceptable "slide off the roof"
|
||||
behavior at the cost of departing from retail's Path 6 → SetCollide →
|
||||
Path 4 → Phase 3 reset chain. Retail-strict requires the
|
||||
step_up_slide / cliff_slide audit below; until that lands, slide-tangent
|
||||
is the right deviation.
|
||||
|
||||
Remaining gaps: real-DAT building-edge fixtures, fuller `cliff_slide`
|
||||
coverage, `NegPolyHit` dispatch, and the retail-strict
|
||||
step_up_slide / cliff_slide audit (filed for follow-up). Named retail
|
||||
anchors include `CTransition::edge_slide`, `CTransition::cliff_slide`,
|
||||
`SPHEREPATH::precipice_slide`, and `SPHEREPATH::step_up_slide`.
|
||||
|
||||
**Files:** `src/AcDream.Core/Physics/TransitionTypes.cs`,
|
||||
`src/AcDream.Core/Physics/BSPQuery.cs`,
|
||||
|
|
@ -214,6 +247,52 @@ cliff/precipice slide, failed step-up/step-down, and the jump-clears-edge case.
|
|||
|
||||
---
|
||||
|
||||
## #35 — [DONE 2026-04-30] Retail debugger toolchain (cdb + PDB GUID matching)
|
||||
|
||||
**Status:** DONE
|
||||
**Severity:** N/A (infrastructure)
|
||||
**Filed + closed:** 2026-04-30
|
||||
**Component:** tooling / research
|
||||
|
||||
**Description:** When the question is "what does retail actually DO at
|
||||
runtime?" — wedges, animation flicker, geometry-specific bugs where the
|
||||
decomp is correct but the visible behavior is mysterious — there was no
|
||||
way to attach a debugger to a live retail acclient.exe and trace it.
|
||||
This issue tracks the toolchain that closed that gap.
|
||||
|
||||
**What shipped:**
|
||||
- **`tools/pdb-extract/check_exe_pdb.py`** — reads any PE's CodeView entry
|
||||
and reports `MATCH` / `MISMATCH (expected GUID = …)` against our
|
||||
`refs/acclient.pdb`. Always run before attaching cdb.
|
||||
- **`tools/pdb-extract/dump_pdb_info.py`** — dumps a PDB's expected
|
||||
build timestamp + GUID + age. Used to figure out which acclient.exe
|
||||
build pairs with our PDB (answer: v11.4186, Sept 2013 EoR).
|
||||
- **CLAUDE.md "Retail debugger toolchain" section** — full workflow:
|
||||
cdb path, sample `.cdb` script, PowerShell wrapper pattern, watchouts
|
||||
(PDB name conventions, `;` parsing, kill-target-on-detach behavior,
|
||||
high-hit-rate lag).
|
||||
- **Step `-1` added to the development workflow** — "ATTACH cdb TO
|
||||
RETAIL (when behavior is the question, not code)". Tells future
|
||||
sessions: when guessing has failed twice in a row, don't keep guessing.
|
||||
|
||||
**Discoveries this toolchain enabled (closed in same session):**
|
||||
- Retail integrates physics at 30Hz (`UpdatePhysicsInternal/update_object`
|
||||
ratio = 0.61). Drove the L.5 fix in PlayerMovementController.
|
||||
- `OBJECTINFO::kill_velocity` rarely fires in normal play (gated on
|
||||
last_known_contact_plane_valid). Our acdream port now matches.
|
||||
- Retail does NOT wedge on the steep-roof scenario. Confirmed our L.4
|
||||
slide-tangent deviation in Path 6 is necessary until the retail
|
||||
step_up_slide / cliff_slide chain audit lands.
|
||||
|
||||
**Files:** `tools/pdb-extract/check_exe_pdb.py`,
|
||||
`tools/pdb-extract/dump_pdb_info.py`, `CLAUDE.md`,
|
||||
`memory/project_retail_debugger.md`.
|
||||
|
||||
**Acceptance:** Future sessions can attach cdb to a live retail client
|
||||
in under 5 minutes by following the CLAUDE.md workflow.
|
||||
|
||||
---
|
||||
|
||||
## #33 — Live entity collision shape collapses to one cylinder
|
||||
|
||||
**Status:** OPEN
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue