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>
382 lines
17 KiB
Markdown
382 lines
17 KiB
Markdown
# acdream — Comprehensive Architecture Plan
|
|
|
|
## Vision
|
|
|
|
A modern C# .NET 10 Asheron's Call client that:
|
|
- **Behaves identically to the retail client** — same physics, same
|
|
animations, same terrain, same collision, same network protocol
|
|
- **Looks identical to the retail client** — same meshes, same textures,
|
|
same lighting, same blending, rendered via modern Silk.NET OpenGL
|
|
- **Adds a plugin API** the retail client never had — native C# plugins
|
|
+ Lua macro scripting for player automation
|
|
- **Is NOT a 1:1 C++ port** — uses modern C# patterns (composition over
|
|
inheritance, interfaces, dependency injection) while matching retail
|
|
behavior exactly
|
|
|
|
## Guiding Principle
|
|
|
|
**The code is modern. The behavior is retail.**
|
|
|
|
Every AC-specific algorithm is ported faithfully from the **named retail
|
|
decomp** at `docs/research/named-retail/` — Sept 2013 EoR build PDB
|
|
(18,366 named functions, 5,371 named struct types) + Binary Ninja
|
|
pseudo-C with 99.6% function-name recovery + verbatim retail header
|
|
struct definitions. The older Ghidra `FUN_xxx` chunks at
|
|
`docs/research/decompiled/` (688K lines) remain a fallback for the
|
|
obfuscated/packed minority. The code AROUND those algorithms is modern
|
|
C# with clean architecture. The plugin API exposes game state through
|
|
well-defined interfaces that the retail client never had.
|
|
|
|
---
|
|
|
|
## Layer Architecture
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ LAYER 5: Plugin API │
|
|
│ IGameState, IEvents, IActions, IPacketPipeline, IOverlay │
|
|
│ Plugin host (ALC), Lua macro engine (MoonSharp) │
|
|
│ ► acdream-unique — not in retail client │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ LAYER 4: Game Objects │
|
|
│ GameEntity (one per world object) │
|
|
│ ├── PhysicsBody (ported from decompiled) │
|
|
│ ├── AnimSequencer (ported from decompiled) │
|
|
│ ├── CellTracker (ported from decompiled) │
|
|
│ ├── AppearanceState (ObjDesc: palettes, textures, parts)│
|
|
│ └── MotionState (ported from decompiled) │
|
|
│ ► behavior matches retail, code is modern C# composition │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ LAYER 3: World Systems │
|
|
│ TerrainSystem (heightmap, blending, scenery) │
|
|
│ CellSystem (LandCells, EnvCells, portals, BSP) │
|
|
│ StreamingSystem (background loading, LOD, frustum cull) │
|
|
│ ► behavior matches retail, streaming is acdream-unique │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ LAYER 2: Network │
|
|
│ WorldSession (ISAAC, fragments, game messages) │
|
|
│ MessageRouter (opcode dispatch, sequence tracking) │
|
|
│ ► wire-format identical to retail │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ LAYER 1: Renderer │
|
|
│ Silk.NET OpenGL 4.3 core profile │
|
|
│ TerrainRenderer, StaticMeshRenderer, TextureCache │
|
|
│ Shaders (terrain blending, mesh lighting, translucency) │
|
|
│ ► completely different from retail (D3D7), same visual │
|
|
│ output │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ LAYER 0: Platform │
|
|
│ .NET 10, Silk.NET window/input, DatReaderWriter │
|
|
│ ► acdream-unique infrastructure │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### UI Architecture (companion stack, spans Layers 1 & 5)
|
|
|
|
The UI is split into its own three-layer stack with a swappable backend,
|
|
designed 2026-04-24. Full design: `docs/plans/2026-04-24-ui-framework.md`.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ UI BACKEND (swappable) │
|
|
│ ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui │
|
|
│ (Phase D.2a, short-term) │
|
|
│ or custom retail-look toolkit (Phase D.2b, later) │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ AcDream.UI.Abstractions (stable contract) │
|
|
│ ViewModels, Commands, IPanel, IPanelHost, IPanelRenderer │
|
|
│ ► plugin-facing UI API lives HERE, not in the backend │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Game state + events (unchanged) │
|
|
│ IGameState / IEvents / WorldSession — UI only reads │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
The backend is pluggable; ViewModels / Commands / `IPanelRenderer` are
|
|
stable across the swap. ImGui persists forever as the
|
|
`ACDREAM_DEVTOOLS=1` devtools overlay regardless of which backend owns
|
|
the game UI. See `memory/project_ui_architecture.md` for the session
|
|
crib-sheet version.
|
|
|
|
---
|
|
|
|
## Project Structure (current + target)
|
|
|
|
```
|
|
src/
|
|
AcDream.Core/ Layer 2-4: no GL, no Silk.NET, pure logic
|
|
Physics/
|
|
PhysicsBody.cs -> body state / integration foundation (done)
|
|
CollisionPrimitives.cs -> retail primitive helpers (partial, active)
|
|
MotionInterpreter.cs -> motion state machine (done, still L.1 polish)
|
|
AnimationSequencer.cs -> animation playback + root-motion data (done, L.1 active)
|
|
TerrainSurface.cs -> triangle-aware terrain contact (done)
|
|
BSPQuery.cs -> partial retail BSP dispatcher (active in L.2)
|
|
TransitionTypes.cs -> SpherePath / CollisionInfo / transition helpers (active in L.2)
|
|
PhysicsDataCache.cs -> GfxObj / Setup / CellStruct collision data (done, active)
|
|
ShadowObjectRegistry.cs -> broadphase for nearby physics objects (active)
|
|
PhysicsEngine.cs -> ResolveWithTransition active player path
|
|
CellBsp.cs -> not a first-class runtime owner yet (L.2e)
|
|
World/
|
|
GameEntity.cs -> target unified entity, not current reality
|
|
WorldState.cs -> target entity owner
|
|
CellTracker.cs -> target per-entity cell management
|
|
SceneryGenerator.cs -> verified against decompiled (done)
|
|
LandblockLoader.cs -> done
|
|
Terrain/
|
|
LandblockMesh.cs -> verified against ACME (done)
|
|
TerrainBlending.cs -> verified against ACME (done)
|
|
Meshing/
|
|
GfxObjMesh.cs -> cross-checked against ACME (done)
|
|
SetupMesh.cs -> cross-checked (done)
|
|
Textures/
|
|
SurfaceDecoder.cs -> done
|
|
Dat/
|
|
MotionResolver.cs -> done (target move from Meshing/)
|
|
|
|
AcDream.Core.Net/ Layer 2: networking
|
|
WorldSession.cs -> done (wire-compatible with ACE)
|
|
NetClient.cs -> done
|
|
Messages/ -> done (CreateObject, MoveToState, etc.)
|
|
|
|
AcDream.Plugin.Abstractions/ Layer 5: plugin interfaces
|
|
IAcDreamPlugin.cs -> done
|
|
IPluginHost.cs -> done
|
|
IGameState.cs -> done
|
|
IEvents.cs -> done
|
|
|
|
AcDream.App/ Layer 1 + Layer 4 wiring
|
|
Rendering/
|
|
GameWindow.cs -> still owns too much runtime wiring
|
|
TerrainRenderer.cs -> done
|
|
StaticMeshRenderer.cs -> done
|
|
TextureCache.cs -> done
|
|
ChaseCamera.cs -> done
|
|
FlyCamera.cs -> done
|
|
Streaming/
|
|
StreamingController.cs -> done
|
|
GpuWorldState.cs -> done
|
|
Input/
|
|
PlayerMovementController.cs -> active movement driver
|
|
Plugins/
|
|
AppPluginHost.cs -> done
|
|
```
|
|
|
|
---
|
|
|
|
## Movement And Collision Architecture
|
|
|
|
Phase L.2 is the current organizing program for physics, collision,
|
|
boundaries, buildings, sliding, cell ownership, movement packets, and server
|
|
authority. Detailed plan: `docs/plans/2026-04-29-movement-collision-conformance.md`.
|
|
|
|
The active player movement spine is:
|
|
|
|
```text
|
|
InputDispatcher / PlayerMovementController
|
|
-> MotionInterpreter + local body prediction
|
|
-> PhysicsEngine.ResolveWithTransition
|
|
-> TransitionTypes + BSPQuery + ShadowObjectRegistry
|
|
-> ResolveResult contact/cell state
|
|
-> MoveToState / AutonomousPosition outbound messages
|
|
-> WorldSession server echo or correction handling
|
|
```
|
|
|
|
What exists and is active:
|
|
|
|
- `PhysicsEngine.ResolveWithTransition` is the path used for local player
|
|
collision resolution.
|
|
- `BSPQuery` contains the partial retail-style BSP collision dispatcher used by
|
|
the transition path.
|
|
- `TransitionTypes` carries `SpherePath`, `CollisionInfo`, `ObjectInfo`,
|
|
transition validation, step-up/down, contact-plane handling, and partial
|
|
slide behavior.
|
|
- `PhysicsDataCache` loads GfxObj, Setup, and CellStruct physics data from DATs.
|
|
- `ShadowObjectRegistry` gives movement a broadphase over nearby objects and
|
|
buildings.
|
|
- `TerrainSurface` uses triangle-aware terrain contact; older "bilinear terrain
|
|
Z" descriptions are historical B.3 language, not current architecture.
|
|
|
|
What remains incomplete:
|
|
|
|
- `CELLARRAY`, `CObjCell::find_cell_list`, adjacent-cell checks, and low outdoor
|
|
cell id updates across 24m seams.
|
|
- `cell_bsp` / `CellBSP` as the authoritative runtime owner for indoor and
|
|
building collision.
|
|
- Building portal transit and normal walking through building entry/exit
|
|
boundaries.
|
|
- Full retail `edge_slide`, `cliff_slide`, `precipice_slide`, and `NegPolyHit`
|
|
dispatch behavior.
|
|
- Exact `CSphere` / `CCylSphere` object-shape parity, especially for live
|
|
entities that currently collapse to a simplified cylinder fallback.
|
|
- Routine local/server correction diagnostics. ACE accepting a position is a
|
|
compatibility signal, not proof of fine retail collision parity.
|
|
|
|
Ownership by phase:
|
|
|
|
- B.3 is shipped MVP history: first resolver foundation and tests.
|
|
- L.1 owns animation/motion parity, including root-motion coupling.
|
|
- L.2 owns the movement/collision conformance stack listed above.
|
|
- G.3 owns dungeon streaming and portal-space delivery after L.2e gives it
|
|
trustworthy cell/building boundaries.
|
|
|
|
---
|
|
|
|
## GameEntity: The Unified Entity (target refactor)
|
|
|
|
Currently, entity state is scattered across:
|
|
- `WorldEntity` (position, rotation, mesh refs)
|
|
- `AnimatedEntity` (animation frame, setup, sequencer)
|
|
- `_entitiesByServerGuid` dict (server GUID lookup)
|
|
- `GpuWorldState._loaded[lb].Entities` (per-landblock lists)
|
|
- `_playerController` (player-specific movement)
|
|
|
|
This should become ONE class:
|
|
|
|
```csharp
|
|
public sealed class GameEntity
|
|
{
|
|
// Identity
|
|
public uint ServerGuid { get; }
|
|
public uint SetupId { get; }
|
|
public string? Name { get; }
|
|
|
|
// Spatial (ported from CPhysicsObj)
|
|
public PhysicsBody Physics { get; } // position, velocity, gravity
|
|
public CellTracker Cell { get; } // which cell we're in
|
|
|
|
// Appearance (ported from CPartArray)
|
|
public AnimationSequencer Animation { get; } // frame playback
|
|
public AppearanceState Appearance { get; } // ObjDesc overrides
|
|
|
|
// Motion (ported from CMotionInterp)
|
|
public MotionInterpreter Motion { get; } // walk/run/turn state
|
|
|
|
// Render output (consumed by StaticMeshRenderer)
|
|
public IReadOnlyList<MeshRef> MeshRefs { get; }
|
|
|
|
// Per-frame update (matches retail update_object)
|
|
public void Update(float dt)
|
|
{
|
|
Motion.ApplyCurrentMovement(); // set velocity from motion state
|
|
Physics.UpdateObject(dt); // integrate position
|
|
PhysicsEngine.ResolveWithTransition(); // current L.2 collision spine
|
|
Cell.UpdateCell(Physics.Position); // target: retail cell ownership
|
|
Animation.Advance(dt); // advance animation frames
|
|
RebuildMeshRefs(); // compute per-part transforms
|
|
}
|
|
}
|
|
```
|
|
|
|
Target state: every entity in the world — player, NPC, monster, lifestone,
|
|
door, chest — becomes a `GameEntity`. The renderer iterates them and draws.
|
|
The plugin API exposes them as `WorldEntitySnapshot`. GameWindow becomes thin.
|
|
|
|
---
|
|
|
|
## Per-Frame Update Order (current runtime)
|
|
|
|
```
|
|
1. Network tick
|
|
└── Drain inbound queue → process CreateObject, UpdateMotion,
|
|
UpdatePosition, PlayerTeleport → create/update GameEntities
|
|
|
|
2. Streaming tick
|
|
└── Compute observer position → load/unload landblocks →
|
|
create terrain + scenery GameEntities
|
|
|
|
3. Input tick (player mode only)
|
|
└── InputDispatcher scopes → PlayerMovementController →
|
|
MotionInterpreter/body prediction → ResolveWithTransition →
|
|
send MoveToState/AutonomousPosition to server
|
|
|
|
4. Entity / animation tick
|
|
└── Current code still has scattered world/entity state. L.1 owns
|
|
animation parity; L.2 owns movement/collision conformance.
|
|
|
|
5. Render tick
|
|
└── Read current entity mesh refs, draw
|
|
TerrainRenderer.Draw, StaticMeshRenderer.Draw
|
|
(frustum cull, translucency pass, etc.)
|
|
|
|
6. Plugin tick
|
|
└── Fire IEvents, drain IActions queue
|
|
|
|
6a. UI tick
|
|
IPanelHost.Draw → iterate registered IPanel instances, build
|
|
ViewModels from IGameState, dispatch user Commands via ICommandBus.
|
|
Backend-agnostic — ImGui or custom retail-look draws here depending
|
|
on which is compiled in. See docs/plans/2026-04-24-ui-framework.md.
|
|
```
|
|
|
|
---
|
|
|
|
## Roadmap Model
|
|
|
|
The old R1-R8 architecture sequence was a useful early refactor sketch, but it
|
|
is no longer the execution plan. The strategic source of truth is now
|
|
`docs/plans/2026-04-11-roadmap.md`, with per-phase details in `docs/plans/`
|
|
and `docs/superpowers/specs/`.
|
|
|
|
Current movement/collision ownership:
|
|
|
|
- **B.3** is shipped MVP history: first collision resolver foundation.
|
|
- **L.1** owns animation/motion parity, including root-motion coupling.
|
|
- **L.2** owns movement and collision conformance:
|
|
`docs/plans/2026-04-29-movement-collision-conformance.md`.
|
|
- **G.3** owns dungeon streaming and portal-space delivery after L.2e lands
|
|
trustworthy `cell_bsp`, `CELLARRAY`, adjacent-cell checks, and building
|
|
entry/exit boundaries.
|
|
|
|
The GameEntity / thin GameWindow refactor remains a valid target architecture,
|
|
but it is not a prerequisite for L.2. Do not resurrect old R1-R8 phase numbers
|
|
for new work; add or update roadmap phases instead.
|
|
|
|
---
|
|
|
|
## Development Workflow (mandatory for ALL work)
|
|
|
|
```
|
|
For every AC-specific behavior:
|
|
|
|
0. GREP NAMED → Search docs/research/named-retail/ by class::method
|
|
1. FALLBACK → Use older docs/research/decompiled/ chunks only if needed
|
|
2. CROSS-CHECK → Verify against ACE + ACME + holtburger where relevant
|
|
3. PSEUDOCODE → Translate to readable pseudocode
|
|
4. PORT → Faithful C# translation
|
|
5. TEST → Conformance test against retail/decomp golden values
|
|
6. INTEGRATE → Surgical wiring into the existing system
|
|
7. VERIFY → Visual + functional test
|
|
```
|
|
|
|
For acdream-specific code (renderer, plugin API, streaming):
|
|
- Design for clean interfaces
|
|
- Test independently
|
|
- No AC-specific magic — those live in the ported layer
|
|
|
|
---
|
|
|
|
## Reference Hierarchy
|
|
|
|
| Domain | Primary Oracle | Secondary |
|
|
|--------|---------------|-----------|
|
|
| Physics/collision | `docs/research/named-retail/` | ACE Physics/ + older decompiled chunks |
|
|
| Animation | `docs/research/named-retail/` + ACE Animation/ | — |
|
|
| Terrain | ACME ClientReference.cs | named retail / older decompiled chunks |
|
|
| Rendering | WorldBuilder (Silk.NET) | ACViewer |
|
|
| Protocol | holtburger | AC2D |
|
|
| Server behavior | ACE | — |
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
The client is "done" when:
|
|
1. You can log in to an ACE server
|
|
2. Walk around the entire world (streaming loads new areas)
|
|
3. Enter and exit buildings through doorways
|
|
4. See all NPCs, monsters, and players animated correctly
|
|
5. Open doors, talk to NPCs, pick up items
|
|
6. Send and receive chat
|
|
7. A Lua macro can automate gameplay
|
|
8. Side-by-side with the retail client, the world looks the same
|