docs: comprehensive architecture plan for acdream
The single most important document in the project. Defines: Architecture: 6-layer stack (Platform → Renderer → Network → World → Game Objects → Plugin API). The code is modern C#; the behavior matches the retail client exactly. GameEntity: the unified entity class that replaces the current scattered state (WorldEntity + AnimatedEntity + guid dicts + player controller). Every world object is a GameEntity with PhysicsBody + AnimationSequencer + CellTracker + MotionInterpreter + AppearanceState. Per-frame update order: Network → Streaming → Input → Entity tick (motion → physics → collision → cell → animation) → Render → Plugin. Execution plan (R1-R8): R1: GameEntity refactor (unify scattered state) R2: Thin GameWindow (extract to proper systems) R3: CellBSP + wall collision (indoor transitions) R4: Complete animation state machine R5: Lighting from decompiled AdjustPlanes R6: Server compliance (authoritative Z, keepalive) R7: Interaction (doors, NPCs, chat, inventory) R8: Plugin API completion (Lua macros) Also updates CLAUDE.md to establish the architect role and reference the architecture doc as the single source of truth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a722c29759
commit
adf626367e
3 changed files with 1199 additions and 13 deletions
343
docs/architecture/acdream-architecture.md
Normal file
343
docs/architecture/acdream-architecture.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# 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 decompiled retail
|
||||
client (docs/research/decompiled/, 688K lines). 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 │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure (target)
|
||||
|
||||
```
|
||||
src/
|
||||
AcDream.Core/ Layer 2-4: no GL, no Silk.NET, pure logic
|
||||
Physics/
|
||||
PhysicsBody.cs ← ported from decompiled (done)
|
||||
CollisionPrimitives.cs ← ported from decompiled (done)
|
||||
MotionInterpreter.cs ← ported from decompiled (done)
|
||||
AnimationSequencer.cs ← ported from decompiled (done)
|
||||
CellBsp.cs ← TODO: port from decompiled
|
||||
Transition.cs ← TODO: port from decompiled
|
||||
TerrainSurface.cs ← verified against ACME (done)
|
||||
World/
|
||||
GameEntity.cs ← TODO: unified entity (replaces scattered state)
|
||||
WorldState.cs ← TODO: owns all entities
|
||||
CellTracker.cs ← TODO: 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 (move here 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 ← TODO: thin down to GL calls only
|
||||
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 ← done (uses ported physics)
|
||||
Plugins/
|
||||
AppPluginHost.cs ← done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GameEntity: The Unified Entity (TODO — the big 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
|
||||
// TODO: Transition.FindValidPosition // collision resolve
|
||||
Cell.UpdateCell(Physics.Position); // check cell transitions
|
||||
Animation.Advance(dt); // advance animation frames
|
||||
RebuildMeshRefs(); // compute per-part transforms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every entity in the world — player, NPC, monster, lifestone, door, chest —
|
||||
is a `GameEntity`. The renderer iterates them and draws. The plugin API
|
||||
exposes them as `WorldEntitySnapshot`. GameWindow becomes thin.
|
||||
|
||||
---
|
||||
|
||||
## Per-Frame Update Order (matches retail)
|
||||
|
||||
```
|
||||
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)
|
||||
└── Read WASD/mouse → MotionInterpreter.DoMotion →
|
||||
send MoveToState/AutonomousPosition to server
|
||||
|
||||
4. Entity tick (ALL entities, 30Hz fixed step)
|
||||
└── For each GameEntity: entity.Update(dt)
|
||||
This runs: motion → physics → collision → cell → animation
|
||||
|
||||
5. Render tick
|
||||
└── For each GameEntity: read MeshRefs, draw
|
||||
TerrainRenderer.Draw, StaticMeshRenderer.Draw
|
||||
(frustum cull, translucency pass, etc.)
|
||||
|
||||
6. Plugin tick
|
||||
└── Fire IEvents, drain IActions queue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Plan: How to Get There
|
||||
|
||||
### Phase R1: GameEntity Refactor (the foundation)
|
||||
**Goal:** Replace the scattered entity state with unified GameEntity.
|
||||
|
||||
1. Create `GameEntity` class in `AcDream.Core/World/`
|
||||
2. Move `AnimatedEntity` fields into `GameEntity.Animation`
|
||||
3. Move `WorldEntity` fields into `GameEntity.Physics` + position
|
||||
4. Move `_entitiesByServerGuid` into `WorldState`
|
||||
5. Move animation tick from `GameWindow.TickAnimations` into `GameEntity.Update`
|
||||
6. GameWindow.OnRender reads `GameEntity.MeshRefs` instead of `WorldEntity.MeshRefs`
|
||||
|
||||
**Test:** Everything looks the same as before. No visual change.
|
||||
|
||||
### Phase R2: Thin GameWindow
|
||||
**Goal:** GameWindow does only GL calls + input dispatch.
|
||||
|
||||
1. Extract entity creation from `OnLiveEntitySpawned` into `WorldState.SpawnEntity`
|
||||
2. Extract motion updates from `OnLiveMotionUpdated` into `WorldState.UpdateMotion`
|
||||
3. Extract player movement from the giant OnUpdate block into `PlayerController`
|
||||
4. GameWindow.OnUpdate calls: network.Tick → streaming.Tick → input.Tick → worldState.Tick → render
|
||||
|
||||
**Test:** Everything works the same. GameWindow.cs drops from 2000+ to ~500 lines.
|
||||
|
||||
### Phase R3: CellBSP + Wall Collision
|
||||
**Goal:** Entities can't walk through walls.
|
||||
|
||||
1. Port CellBSP from decompiled code (sphere_intersects_cell)
|
||||
2. Port Transition.FindValidPosition (swept sphere collision)
|
||||
3. Wire into GameEntity.Update between physics and cell tracking
|
||||
4. Indoor transitions become correct (wall stops you, doorway lets you through)
|
||||
|
||||
**Test:** Walk into building wall → stopped. Walk through doorway → enter.
|
||||
|
||||
### Phase R4: Complete Animation State Machine
|
||||
**Goal:** Every animation works for every entity type.
|
||||
|
||||
1. Port full MotionInterp.PerformMovement from decompiled (all 5 movement types)
|
||||
2. Port Links table resolution for smooth transitions
|
||||
3. Port idle modifiers (fidgets)
|
||||
4. Jump animation (wire jump motion command through the pipeline)
|
||||
|
||||
**Test:** All entity types animate correctly. Transitions are smooth.
|
||||
|
||||
### Phase R5: Lighting from Retail
|
||||
**Goal:** Sun, ambient, per-vertex lighting match retail.
|
||||
|
||||
1. Port AdjustPlanes (FUN_00532440) — face normals + per-vertex lighting
|
||||
2. Extract global lighting constants from decompiled DAT addresses
|
||||
3. Replace hardcoded shader constants with ported values
|
||||
|
||||
**Test:** Side-by-side with retail client shows matching lighting.
|
||||
|
||||
### Phase R6: Server Compliance
|
||||
**Goal:** ACE accepts all movement, no rubber-banding.
|
||||
|
||||
1. Server-authoritative Z (trust server position, local is cosmetic)
|
||||
2. Proper MoveToState with full RawMotionState packing
|
||||
3. Keepalive ping (5s idle)
|
||||
4. Graceful session management
|
||||
|
||||
**Test:** Walk around, other clients see smooth movement. No ACE errors.
|
||||
|
||||
### Phase R7: Interaction
|
||||
**Goal:** Click NPCs, open doors, pick up items, chat.
|
||||
|
||||
1. Use/UseWithTarget game actions
|
||||
2. Door open animation (server sends UpdateMotion → animate)
|
||||
3. Chat send/receive
|
||||
4. Basic inventory (pickup/drop)
|
||||
|
||||
**Test:** Open a door, talk to an NPC, send a chat message.
|
||||
|
||||
### Phase R8: Plugin API Completion
|
||||
**Goal:** Plugins can observe and control everything.
|
||||
|
||||
1. IGameState exposes all GameEntity fields
|
||||
2. IEvents fires for all world changes
|
||||
3. IActions covers: Move, Cast, Use, Say, Pickup, Drop
|
||||
4. IPacketPipeline hooks all 4 stages
|
||||
5. Lua macro engine (MoonSharp) ships as a built-in plugin
|
||||
|
||||
**Test:** A Lua script auto-loots gems. A C# plugin displays an overlay.
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow (mandatory for ALL work)
|
||||
|
||||
```
|
||||
For every AC-specific behavior:
|
||||
|
||||
1. DECOMPILE → Find the function in docs/research/decompiled/
|
||||
2. CROSS-CHECK → Verify against ACE + ACME + holtburger
|
||||
3. PSEUDOCODE → Translate to readable pseudocode
|
||||
4. PORT → Faithful C# translation
|
||||
5. TEST → Conformance test against decompiled 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 | Decompiled acclient.exe | ACE Physics/ |
|
||||
| Animation | Decompiled + ACE Animation/ | — |
|
||||
| Terrain | ACME ClientReference.cs | Decompiled |
|
||||
| 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue