CLAUDE.md edits (6 surgical ranges):
- Goal section: introduce named-retail/ as primary; old chunks
remain as fallback for chunk-by-chunk address-range navigation.
- Workflow renamed to "grep named -> decompile -> verify -> port"
with a new STEP 0 GREP NAMED FIRST. Decompile demoted to a
fallback (Step 1) for the rare obfuscated/packed minority that
pseudo-C lacks.
- Function-map citation updated to point at symbols.json + the
cross-port hand-curated table.
- "Do not guess" rule strengthened: PDB has the answer for almost
everything; guessing is now negligence.
- Phase completion checklist accepts named symbols + addresses.
- Reference hierarchy table gets a new top row pointing at
docs/research/named-retail/ as the primary oracle for any
AC-specific algorithm — beats every other reference.
memory/project_named_decompilation.md (new): evergreen crib-sheet
with file inventory, grep examples, hard rules. Pattern matches
project_ui_architecture.md.
memory/project_retail_research_index.md: updated preamble to point
named-retail/ as first stop; older slices remain useful for
pseudocode + C# port sketches.
memory/project_collision_port.md: rewrote the "Decompiled ground
truth" section to put named-retail/ first, chunks second. The
"DECOMPILE FIRST" mandate becomes "GREP NAMED FIRST, then DECOMPILE
FALLBACK".
docs/architecture/acdream-architecture.md: Guiding Principle text
updated to introduce named-retail as the primary decomp source.
docs/plans/2026-04-11-roadmap.md: new Phase R block — Retail
research infrastructure. R.1 (corpus, shipped a9a01d8), R.2
(pdb-extract, shipped 69d884a), R.3 (actestclient vendored,
shipped a9a01d8). All marked SHIPPED 2026-04-25.
Auto-loaded MEMORY.md index updated with a new entry pointing at
project_named_decompilation.md so post-compaction sessions inherit
the workflow change automatically.
Acceptance verified:
- grep -c "named-retail" CLAUDE.md = 9 (>= 3 required)
- grep -c "named-retail" MEMORY.md = 1
- dotnet build green (docs-only commit, but verified)
Foundation phases A + B + C all landed. Next: Phase D files
ISSUES #8/#9/#11 + closes #10 (KillerNotification orphan parser).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
381 lines
17 KiB
Markdown
381 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 (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
|
|
|
|
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.
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|