New static `AcDream.Core.Physics.PhysicsDiagnostics` holds two runtime-toggleable flags initialized from env vars: - ACDREAM_PROBE_RESOLVE=1 — emit one [resolve] line per PhysicsEngine.ResolveWithTransition call: input/target/output position+cell, ok-vs-partial, grounded-in, contact-plane status, wall normal if hit, walkable-polygon valid, moving entity id. - ACDREAM_PROBE_CELL=1 — emit one [cell-transit] line per PlayerMovementController.CellId change: old → new cell, current world position, reason tag (resolver / teleport). Both also exposed as runtime-toggleable checkboxes in the DebugPanel "Diagnostics" section. Unlike the existing four Dump-* checkboxes (which only mirror sticky-at-startup env vars), the two new ones forward directly to PhysicsDiagnostics — toggling on/off takes effect on the next physics resolve, no relaunch. Why now: L.2's plan-of-record (docs/plans/2026-04-29-movement-collision- conformance.md) explicitly says "Land L.2a diagnostics first. Do not make another physics change blind." This slice closes the most-load- bearing gap in L.2a — a general-purpose probe on the resolver outcome and a cell-transit log — so that later L.2b/c/d/e physics changes can be evidence-driven instead of guessed. Foundation for the indoor / dungeon walking trajectory (G.3 unblock). Pure additive: when both flags are off (default), the probes collapse to a single static-bool read per resolve, zero log cost. PlayerMovement Controller's two CellId-mutation sites are now routed through a private UpdateCellId(reason) helper for diag chokepoint. Build green, 1032/1040 unit tests pass. The 8 failing tests are pre-existing on the branch base (verified by stash-and-rerun); none touch resolver or cell-transit code; all fail identically with this slice stashed. Investigation deferred to a follow-up. Refs: docs/plans/2026-04-29-movement-collision-conformance.md (L.2a shipped-slice note added in same commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
247 lines
11 KiB
Markdown
247 lines
11 KiB
Markdown
# Phase L.2 - Movement & Collision Conformance
|
|
|
|
**Status:** ACTIVE planning document, created 2026-04-29.
|
|
**Roadmap owner:** Phase L.2 in `docs/plans/2026-04-11-roadmap.md`.
|
|
**Scope:** player movement prediction, retail collision/transition behavior,
|
|
building boundaries, edge and wall sliding, cell ownership, outbound movement
|
|
packets, and server-correction diagnostics.
|
|
|
|
## Purpose
|
|
|
|
Phase B.3 shipped the first usable physics foundation: terrain contact,
|
|
basic resolver behavior, streaming-populated collision inputs, and enough
|
|
movement wire support to walk on ACE. That was not the complete retail
|
|
collision system.
|
|
|
|
Phase L.2 is the conformance program that turns that foundation into a
|
|
retail-faithful movement stack. It is the single organizing bucket for work
|
|
that otherwise looks scattered across B.3 physics, L.1 animation/motion, and
|
|
G.3 dungeon/portal space.
|
|
|
|
The active movement spine is:
|
|
|
|
```text
|
|
input + motion command
|
|
-> local body prediction / root-motion source
|
|
-> PhysicsEngine.ResolveWithTransition
|
|
-> TransitionTypes + BSPQuery + ShadowObjectRegistry contact/cell result
|
|
-> MoveToState / AutonomousPosition outbound packets
|
|
-> server echo or correction diagnostics
|
|
```
|
|
|
|
Live ACE accepting a position, or the absence of visible rubber-banding, is
|
|
not proof of retail collision parity. ACE can tolerate coarse or locally
|
|
invalid fine-grained movement. L.2 therefore requires retail-decomp evidence,
|
|
synthetic conformance tests, real-DAT fixtures, and live retail-observer checks.
|
|
|
|
## Current Foundation
|
|
|
|
Already active in acdream:
|
|
|
|
- `PhysicsEngine.ResolveWithTransition` is the local player collision path.
|
|
- `BSPQuery` contains a partial retail-style BSP dispatcher and step/contact
|
|
logic.
|
|
- `TransitionTypes` carries `SpherePath`, `CollisionInfo`, `ObjectInfo`,
|
|
transition validation, step-up/down, and partial slide behavior.
|
|
- `PhysicsDataCache` loads GfxObj, Setup, and CellStruct physics data from DATs.
|
|
- `ShadowObjectRegistry` gives the resolver a broadphase over nearby world
|
|
objects.
|
|
- `TerrainSurface` uses triangle-aware terrain sampling rather than the older
|
|
bilinear placeholder.
|
|
|
|
Known incomplete areas:
|
|
|
|
- Full `CELLARRAY` ownership and `CObjCell::find_cell_list` / adjacent-cell
|
|
checks are not ported.
|
|
- `cell_bsp` / `CellBSP` is not fully represented as a first-class runtime
|
|
owner.
|
|
- Building entry/exit and indoor/outdoor portal transit are not solved by the
|
|
normal walking path.
|
|
- Retail `edge_slide`, `cliff_slide`, and `precipice_slide` behavior is
|
|
incomplete; failed edge/step-down cases often hard-block instead of sliding.
|
|
- `NegPolyHit` handling is a stub relative to the retail transition dispatch.
|
|
- Live entities collapse to a simplified cylinder shape; exact retail
|
|
sphere/cylsphere and object-shape behavior is not yet matched.
|
|
- Outbound contact/cell fields can be too optimistic, so server agreement does
|
|
not necessarily mean local conformance.
|
|
|
|
## Lane Model
|
|
|
|
L.2 uses five working lanes. The roadmap breaks them into six sub-lanes because
|
|
real-DAT and live verification spans every lane.
|
|
|
|
| Lane | Owns | Roadmap slice |
|
|
|---|---|---|
|
|
| Diagnostics | Truth probes, dump flags, server-correction logging, retail observer harness | L.2a, L.2f |
|
|
| Transition parity | `FindTransitionalPosition`, step-up/down, edge-slide, cliff-slide, precipice-slide, `NegPolyHit` dispatch | L.2c |
|
|
| Geometry fidelity | `CSphere`, `CCylSphere`, object shape extraction, building object collision, walkable polygon context | L.2d |
|
|
| Cell/building ownership | outdoor cell seams, low-cell id updates, `CELLARRAY`, `cell_bsp`, building entry/exit | L.2e |
|
|
| Movement/network authority | contact byte, full cell id, MoveToState / AutonomousPosition cadence, root motion vs velocity prediction, correction response | L.2b, L.2f |
|
|
|
|
## Roadmap Slices
|
|
|
|
### L.2a - Truth & Diagnostics
|
|
|
|
Goal: make every bad movement outcome explainable.
|
|
|
|
- Add targeted diagnostics for local placement, contact plane, object hit,
|
|
water, cell id, outbound packet fields, server echo, and correction delta.
|
|
- Keep diagnostics opt-in via env vars and devtools panels.
|
|
- Record enough data for side-by-side retail-observer runs without drowning
|
|
normal logs.
|
|
- Build real-DAT fixture capture for known walls, building ledges, rooftops,
|
|
slopes, landblock seams, and dungeon entrances.
|
|
|
|
Current shipped slices:
|
|
|
|
- 2026-04-30: cdb + TTD retail-observer toolchain (`tools/pdb-extract/`,
|
|
`tools/ttd-record.ps1`, `tools/ttd-query.ps1`) with PDB pairing checker
|
|
and ring-buffer trace replay. The "retail observer harness" line item.
|
|
- 2026-04 (pre-L.2 rename): `ACDREAM_DUMP_MOVE_TRUTH` paired
|
|
outbound/server-echo dumper in `GameWindow` covers outbound packet
|
|
fields + server echo + correction delta with cell-id mismatch.
|
|
- Pre-L.2: scenario-specific dumps `ACDREAM_DUMP_MOTION`,
|
|
`ACDREAM_DUMP_STEEP_ROOF`, `ACDREAM_DUMP_STEPUP`,
|
|
`ACDREAM_DUMP_EDGE_SLIDE` for the codepaths hit during prior bug chases.
|
|
- 2026-05-12 (slice 1): general-purpose probes via new
|
|
`AcDream.Core.Physics.PhysicsDiagnostics` static class.
|
|
`ACDREAM_PROBE_RESOLVE` emits one `[resolve]` line per
|
|
`PhysicsEngine.ResolveWithTransition` call (input/output pos+cell,
|
|
ok-vs-partial, grounded-in, contact-plane status, wall normal if hit,
|
|
walkable polygon valid, moving entity id).
|
|
`ACDREAM_PROBE_CELL` emits one `[cell-transit]` line per
|
|
`PlayerMovementController.CellId` change with old→new + position +
|
|
reason tag (`resolver`/`teleport`). Both flippable live via the
|
|
DebugPanel "Diagnostics" section — checkbox toggles take effect on
|
|
the next resolve, no relaunch required.
|
|
|
|
Remaining L.2a work: contact-plane probe (general, not just steep-roof),
|
|
ShadowObjectRegistry hit log ("you collided with entity X"), water probe,
|
|
real-DAT fixture-capture pipeline, and folding the older sticky-at-startup
|
|
`ACDREAM_DUMP_*` flags into `PhysicsDiagnostics` for unified runtime
|
|
toggling.
|
|
|
|
### L.2b - Movement Wire / Contact Authority
|
|
|
|
Goal: stop sending movement packets that claim more certainty than the local
|
|
resolver has earned.
|
|
|
|
- Fix outbound contact state so `AutonomousPosition` and `MoveToState` do not
|
|
always claim grounded contact.
|
|
- Track local result cell id and outbound full cell id separately from the last
|
|
server placement until correction proves they agree.
|
|
- Reconcile packet cadence with retail/holtburger references.
|
|
- Wire routine server correction handling and diagnostics, not only portal
|
|
reseating.
|
|
|
|
### L.2c - Transition Parity: Edge / Slide / Neg-Poly
|
|
|
|
Goal: match retail movement at walls, roof edges, step boundaries, and
|
|
precipices.
|
|
|
|
- Port and test `edge_slide`, `cliff_slide`, `precipice_slide`, and
|
|
`step_up_slide` behavior from named retail.
|
|
- Preserve walkable polygon context needed for precipice/edge decisions.
|
|
- Replace `NegPolyHit` stub behavior with the retail dispatch path.
|
|
- Confirm the user-visible rule: walk-only motion is blocked by step,
|
|
edge, walkable, and collision rules; jumping clears `OnWalkable` and only
|
|
succeeds when the airborne path actually clears geometry.
|
|
|
|
Current shipped slice (2026-04-30): wall-adjacent `step_up_slide` feels
|
|
acceptable in live testing; player/remote movers pass `EdgeSlide`; terrain and
|
|
BSP step-down/find-walkable now preserve walkable polygon vertices; failed
|
|
step-down edge cases perform the retail back-probe before
|
|
`SPHEREPATH::precipice_slide`; precipice slide results now re-enter the
|
|
`TransitionalInsert` retry loop so tangent edge motion is preserved instead of
|
|
being reverted by outer validation. Remaining L.2c work is live visual
|
|
confirmation at real building/roof edges, real-DAT building-edge fixtures,
|
|
fuller `cliff_slide` coverage, and `NegPolyHit` dispatch.
|
|
|
|
### L.2d - Shape Fidelity: Sphere / CylSphere / Building Objects
|
|
|
|
Goal: object collisions use retail shape semantics, not one simplified
|
|
fallback.
|
|
|
|
- Finish `CSphere` / `CCylSphere` parity for static and live objects.
|
|
- Stop treating all live entities as one root-centered cylinder.
|
|
- Preserve enough building identity to model `CBuildingObj` collision and
|
|
`bldg_check` behavior.
|
|
- Audit `Setup.Radius` and cylinder fallback behavior against retail before
|
|
relying on them for conformance.
|
|
|
|
### L.2e - Cell Ownership: Outdoor Seams, CELLARRAY, cell_bsp
|
|
|
|
Goal: the resolver knows which cell owns the movement and which adjacent cells
|
|
must be checked.
|
|
|
|
- Update low outdoor cell id across 24m cell boundaries and landblock seams.
|
|
- Port the retail adjacent-cell search: `find_cell_list`, `check_other_cells`,
|
|
and `adjust_check_pos`.
|
|
- Promote `cell_bsp` / `CellBSP` from partial data to active runtime owner.
|
|
- Hand G.3 a trustworthy building/portal boundary so dungeon streaming is not
|
|
asked to solve collision ownership after the fact.
|
|
|
|
### L.2f - Real-DAT and Live Retail-Observer Conformance
|
|
|
|
Goal: prove the stack against real terrain/building/cell data and what a retail
|
|
client sees when observing acdream.
|
|
|
|
- Add real-DAT fixtures for representative movement cases.
|
|
- Use retail client observer runs to verify motion packets, animation/movement
|
|
coupling, and server-visible placement.
|
|
- Treat ACE acceptance as a coarse compatibility check only.
|
|
- Require conformance notes in tests or research docs for every AC-specific
|
|
algorithm ported under L.2.
|
|
|
|
## Named Retail Anchors
|
|
|
|
Primary source: `docs/research/named-retail/acclient_2013_pseudo_c.txt`.
|
|
Struct source: `docs/research/named-retail/acclient.h`.
|
|
Address lookup: `docs/research/named-retail/symbols.json`.
|
|
|
|
Use these names before falling back to older `docs/research/decompiled/`
|
|
chunks:
|
|
|
|
- `CTransition::find_transitional_position` - `0x0050BDF0`
|
|
- `CTransition::transitional_insert` - `0x0050B6F0`
|
|
- `CTransition::step_up` - `0x0050B610`
|
|
- `CTransition::step_down` - `0x0050B2A0`
|
|
- `CTransition::edge_slide` - `0x0050B3D0`
|
|
- `CTransition::cliff_slide` - `0x0050A6D0`
|
|
- `SPHEREPATH::step_up_slide` - `0x0050C3B0`
|
|
- `SPHEREPATH::precipice_slide` - `0x0050CC80`
|
|
- `SPHEREPATH::adjust_check_pos` - `0x0050CC00`
|
|
- `CTransition::adjust_offset` - `0x0050A370`
|
|
- `CTransition::check_other_cells` - `0x0050AE50`
|
|
- `CPhysicsObj::is_valid_walkable` - `0x0050F530`
|
|
- `CObjCell::find_cell_list` - `0x0052B4E0`
|
|
- `CBuildingObj::find_building_collisions`
|
|
- `CCellStruct::point_in_cell`
|
|
- `CCellStruct::sphere_intersects_cell`
|
|
- `CCellStruct::box_intersects_cell`
|
|
- `CCylSphere::intersects_sphere`
|
|
- `CSphere::intersects_sphere`
|
|
- `CSphere::slide_sphere`
|
|
|
|
## Implementation Order
|
|
|
|
1. Land L.2a diagnostics first. Do not make another physics change blind.
|
|
2. Fix L.2b packet/contact truth so logs and server echoes describe reality.
|
|
3. Port L.2c transition parity in narrow slices with named-retail citations and
|
|
conformance tests.
|
|
4. Improve L.2d shape fidelity where transition parity depends on object
|
|
contact semantics.
|
|
5. Land L.2e cell/building ownership before G.3 dungeon/portal work relies on
|
|
indoor/outdoor walking.
|
|
6. Promote each synthetic case to L.2f real-DAT and live observer coverage.
|
|
|
|
## Acceptance
|
|
|
|
- A developer can name the active movement path and the current incomplete
|
|
pieces without reading old chat logs.
|
|
- `dotnet build` and `dotnet test` stay green for each implementation slice.
|
|
- Every AC-specific port cites named retail decomp or a documented fallback.
|
|
- Real-DAT fixtures cover buildings, walls, roof edges, outdoor seams, and at
|
|
least one dungeon/building entrance path before L.2 is marked shipped.
|
|
- Retail observer view and acdream local view both agree on contact, position,
|
|
and movement state for the representative cases.
|