docs(physics): Phase L.2 movement collision conformance plan

Formalize Phase L.2 as the active holistic movement/collision program, align the roadmap and architecture docs, file tactical physics follow-ups, and refresh collision memory away from rewrite-from-zero guidance.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-04-29 21:28:56 +02:00
parent e44d24cec6
commit d4c3f947d2
6 changed files with 689 additions and 242 deletions

View file

@ -1,6 +1,6 @@
# acdream — strategic roadmap
**Status:** Living document. Updated 2026-04-11 after Phase 6, 7.1, 9.1, 9.2 landed.
**Status:** Living document. Updated 2026-04-29 for Phase L.2 movement/collision conformance planning.
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@ -31,7 +31,7 @@
| A.1 | Streaming landblock loader — runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS`), camera-centered offline / player-centered live, hysteresis-based unloads, pending-spawn list for late CreateObject events | Live ✓ |
| A.2 | Frustum culling — per-landblock AABB test (Gribb-Hartmann), terrain + static-mesh renderers skip culled landblocks, perf overlay in window title | Visual ✓ |
| A.3 | Background net receive thread — dedicated daemon thread buffers UDP into Channel, render thread drains | Visual ✓ |
| B.3 | Physics collision engine — TerrainSurface (heightmap Z), CellSurface (indoor floor polygon projection), PhysicsEngine (resolver with step-height + cell transitions). Populated from streaming pipeline. | Tests ✓ |
| B.3 | Physics MVP resolver foundation — terrain contact, CellSurface prototype, streaming-populated collision inputs, and first `PhysicsEngine` resolver path. Not the complete retail collision system. | Tests ✓ |
| B.2 | Player movement mode — Tab-toggled WASD ground walking, walk/run/idle animations, third-person chase camera, MoveToState + AutonomousPosition outbound, portal entry. Outdoor-only MVP. | Live ✓ |
| D.1 | 2D ortho overlay + font rendering (StbTrueTypeSharp atlas + TextRenderer + DebugOverlay) | Visual ✓ |
| E.1 | Motion-hook expansion — AnimationSequencer fires all 27 hook types per crossed frame; PosFrames root motion + vel/omega exposure; IAnimationHookSink + AnimationHookRouter fan-out | Tests ✓ |
@ -94,7 +94,7 @@ Plus polish that doesn't get its own phase number:
**Sub-pieces:**
- **✓ SHIPPED — B.1 — Outbound ack pump.** Shipped as Phase 4.9 — per-packet ACK_SEQUENCE, not periodic. Server no longer drops idle clients.
- **✓ SHIPPED — B.2 — Player movement mode.** Tab-toggled WASD ground walking with collision-resolved outdoor terrain, walk/run/idle/turn-right animations, third-person chase camera, outbound MoveToState (0xF61C) + AutonomousPosition (0xF753) server messages, portal entry works. Outdoor→indoor transition disabled for MVP (CellSurface floor polygons too aggressive without portal-based detection). Minor polish remaining: strafe animation, turn-left animation. Spec: `docs/superpowers/specs/2026-04-12-player-movement-design.md`.
- **✓ SHIPPED — B.3 — Physics collision engine.** TerrainSurface (heightmap bilinear Z), CellSurface (indoor floor polygon projection via barycentric interpolation), PhysicsEngine (top-level resolver with step-height enforcement, outdoor↔indoor cell transitions, gravity reporting). Populated from streaming pipeline. 16 unit tests with fake data. Spec: `docs/superpowers/specs/2026-04-12-physics-collision-engine-design.md`.
- **✓ SHIPPED — B.3 — Physics MVP resolver foundation.** Terrain contact, CellSurface prototype, streaming-populated collision inputs, and first `PhysicsEngine` resolver path. This shipped enough foundation for outdoor walking and early portal experiments, but it is not the complete retail collision system. Current conformance work lives under **Phase L.2 — Movement & Collision Conformance**. Spec history: `docs/superpowers/specs/2026-04-12-physics-collision-engine-design.md`.
- **B.4 — `Use` / `UseWithTarget` / `PickUp`.** Outbound interaction messages. Drives opening doors, looting, talking to vendors.
- **B.5 — Chat.** `SendTell`, `SendChat` outbound + receive/display inbound (display side depends on Phase D.1).
@ -204,7 +204,7 @@ Research: R9 + R12 + R13.
- **✓ SHIPPED — G.1 — Sky + weather + day-night.** Deterministic client-side from Portal Year time. Sky dome geometry + keyframe gradients + rain/snow particles. See `r12-weather-daynight.md`. Full data + visual stack shipped: Region dat loader, keyframe interp, WeatherSystem with 5-kind PDF + transitions + storm flashes, WorldSession→WorldTimeService sync via ConnectRequest+TimeSync, SkyRenderer with sky-object arcs + UV scroll, rain/snow billboard renderer, F7/F10 debug cycle keys.
- **✓ SHIPPED — G.2 — Dynamic lighting.** 8-light D3D-style fixed pipeline. Hard-cutoff at Range, no attenuation inside. Cell ambient. Shader UBO per frame. See `r13-dynamic-lighting.md`. SceneLightingUbo std140 at binding=1 feeds terrain + mesh + mesh_instanced + sky shaders. LightingHookSink auto-registers Setup.Lights at entity stream-in, flips IsLit on SetLightHook, unregisters on landblock unload.
- **G.3 — Dungeon streaming + portal space.** `EnvCellStreamer`, portal-visibility BFS, `PlayerTeleport (0xF751)` handling with `LoginComplete` re-send, "pink bubble" loading state. See `r09-dungeon-portal-space.md`.
- **G.3 — Dungeon streaming + portal space.** `EnvCellStreamer`, portal-visibility BFS, `PlayerTeleport (0xF751)` handling with `LoginComplete` re-send, "pink bubble" loading state. **Blocked on L.2e** for trustworthy `cell_bsp`, indoor/outdoor portal transit, adjacent-cell ownership, and building entry/exit collision boundaries. See `r09-dungeon-portal-space.md`.
**Acceptance:** walk outside at dusk, see the sky gradient + sun moving; enter a torch-lit dungeon via portal; leave back to daylight.
@ -318,6 +318,11 @@ queues, speed scaling, and PosFrame root motion.
**Plan of record:** `docs/plans/animation-system-audit.md`.
**Coupling to L.2:** L.1 owns animation/motion parity. L.2 owns collision,
contact truth, movement packets, and server-visible placement. They meet where
root motion or observer movement changes the predicted body path; any such
change must keep both phase plans in sync.
**Sub-pieces:**
- **L.1a — Audit & inventory.** Map retail named-decomp evidence, ACE
cross-references, existing acdream hook points, and current gaps for each
@ -349,6 +354,55 @@ queues, speed scaling, and PosFrame root motion.
---
### Phase L.2 — Movement & Collision Conformance
**Status:** ACTIVE.
**Goal:** make acdream's movement and collision behavior retail-faithful across
terrain, buildings, walls, roof edges, cell seams, portal boundaries, outbound
movement packets, and server correction. This is the holistic bucket for the
work previously scattered across B.3 physics follow-ups, L.1 motion coupling,
and G.3 dungeon/portal ownership.
**Plan of record:** `docs/plans/2026-04-29-movement-collision-conformance.md`.
**Current foundation:** `PhysicsEngine.ResolveWithTransition`,
`BSPQuery`, `TransitionTypes`, `PhysicsDataCache`, and
`ShadowObjectRegistry` exist and are active. They are partial retail ports and
diagnostic scaffolding, not yet the final collision system.
**Sub-lanes:**
- **L.2a — Truth & diagnostics.** Local placement/contact/cell logs, object-hit
probes, correction-delta diagnostics, retail-observer capture workflow, and
real-DAT fixture capture.
- **L.2b — Movement wire/contact authority.** Fix outbound contact truth,
full-cell id handling, packet cadence, and routine server correction handling.
- **L.2c — Transition parity: edge/slide/neg-poly.** Port and test retail
`edge_slide`, `cliff_slide`, `precipice_slide`, step-up/down slide, and
`NegPolyHit` dispatch behavior.
- **L.2d — Shape fidelity: sphere/cylsphere/building objects.** Finish
`CSphere` / `CCylSphere` parity, live-entity object shapes, building object
collision identity, and `Setup.Radius` fallback audit.
- **L.2e — Cell ownership: outdoor seams, `CELLARRAY`, `cell_bsp`.** Update
low outdoor cell id across 24m seams, port adjacent-cell checks, activate
`cell_bsp`, and hand G.3 a trustworthy building/portal boundary model.
- **L.2f — Real-DAT and live retail-observer conformance.** Promote synthetic
tests to real-world fixtures and verify local acdream view plus retail
observer view. ACE accepting a position is a compatibility check, not proof
of fine-grained retail collision parity.
**Acceptance:**
- A developer can trace the active movement path: input/motion -> body
prediction -> `ResolveWithTransition` -> contact/cell result -> outbound
packets -> server echo/correction.
- Buildings, edge-slide, wall-slide, cell seams, packet authority, and dungeon
portal ownership each have an L.2 lane.
- Every AC-specific algorithm port cites named retail decomp, or a documented
fallback when named retail lacks the body.
- `dotnet build` and `dotnet test` are green for each implementation slice.
---
### Phase J — Long-tail (deferred / low-priority)
Not detailed here; each gets its own brainstorm when it becomes relevant.
@ -431,7 +485,10 @@ port in any phase — no separate listing here.
| Holtburg sign half-buried | **5 FIXED** ✓ |
| Can't walk past the loaded 3×3 window | **A.1 FIXED** ✓ (5×5 default, `ACDREAM_STREAM_RADIUS` to tune) |
| Frame hitch crossing landblock boundary | **Phase A.3** (synchronous loader for now; async returns when DatCollection is thread-safe) |
| Walking around doesn't move me on the server | **Phase B.3 FIXED** ✓ |
| Walking around doesn't move me on the server | **Phase B.2/B.3 FIXED** ✓ for coarse server movement; fine retail collision parity is **Phase L.2** |
| Sliding along buildings / walls feels wrong | **Phase L.2c + L.2d** |
| Roof edge / cliff / precipice blocks or slides wrong | **Phase L.2c** |
| Crossing outdoor cell seams reports the wrong cell | **Phase L.2e** |
| Can't talk to NPCs | **Phase H.3** (emote scripts + dialogs) |
| Can't open a door | **Phase F** (object-use action) |
| Portals render as a rotating black disk | **Phase E.3** (particle system) |
@ -444,7 +501,7 @@ port in any phase — no separate listing here.
| Combat doesn't show in the chat log | **I.7 FIXED** ✓ |
| Accented character names show as `?` or garbled | **I.5 FIXED** ✓ (Windows-1252 codec) |
| No sound | **Phase E.2** |
| Dungeons / foundry interior missing | **Phase G.3** |
| Dungeons / foundry interior missing | **Phase G.3** after **L.2e** cell/building ownership |
| Can't fight monsters | **Phase F.3** (combat math + damage) |
| Can't cast spells | **Phase F.4** |
| No inventory panel | **Phase F.2 + F.5** |

View file

@ -0,0 +1,208 @@
# 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.
### 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.
### 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.