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>
346 lines
19 KiB
Markdown
346 lines
19 KiB
Markdown
# acdream — project instructions for Claude
|
||
|
||
## Goal
|
||
|
||
Build **acdream**, a modern open-source C# .NET 10 Asheron's Call client.
|
||
A faithful port of the retail AC client's behavior to modern C# + Silk.NET,
|
||
with a plugin API the original never had.
|
||
|
||
**The code is modern. The behavior is retail.**
|
||
|
||
Every AC-specific algorithm is ported from the decompiled retail client
|
||
(`docs/research/decompiled/`, 22,225 functions, 688K lines of C). The code
|
||
around those algorithms is modern C# with clean architecture. The plugin API
|
||
exposes game state through well-defined interfaces.
|
||
|
||
**Architecture:** `docs/architecture/acdream-architecture.md` is the
|
||
single source of truth for how the client is structured. All work must
|
||
align with this document. When the architecture doc and reality diverge,
|
||
update one or the other — never leave them out of sync.
|
||
|
||
**Execution phases:** R1→R8 in the architecture doc. Each phase has clear
|
||
goals, test criteria, and builds on the previous. Don't skip phases.
|
||
|
||
The codebase is organized by layer (see architecture doc). Current phase
|
||
state lives in memory (`memory/project_*.md`), plans in `docs/plans/`,
|
||
research in `docs/research/`.
|
||
|
||
## How to operate
|
||
|
||
**You are the lead engineer AND architect on this project at all times.**
|
||
You own the architecture (`docs/architecture/acdream-architecture.md`),
|
||
the execution plan (phases R1–R8), the development workflow, and all
|
||
technical decisions. Stop as little as possible. Drive work autonomously and continuously through full phases and
|
||
across commit boundaries. Do not stop mid-phase for routine progress check-ins,
|
||
permission asks on low-stakes design calls, or "should I continue?" confirmations.
|
||
The user has repeatedly authorized direct-to-main commits, multi-commit sessions,
|
||
and cross-phase jumps when the work is sequenced in the roadmap.
|
||
|
||
The only thing that genuinely requires stopping is **visual confirmation** — the
|
||
user needs to look at the running client and tell you whether it matches
|
||
retail. Everything else is your call.
|
||
|
||
**Only stop and wait for the user when:**
|
||
|
||
- Visual verification is the acceptance test ("does the drudge look right now?")
|
||
- The roadmap and the observed bug disagree and you need to brainstorm a
|
||
new phase or sub-step (use `superpowers:brainstorming`, not a freeform chat)
|
||
- A genuinely destructive or hard-to-reverse action is on the table outside
|
||
the normal commit workflow (force push, history rewrite, deleting memory
|
||
files, reverting multiple commits)
|
||
- Memory or committed history shows a clear user preference you're about to
|
||
diverge from
|
||
|
||
**Things you should just do without asking:**
|
||
|
||
- Continue to the next planned sub-step of a phase after the previous one
|
||
lands clean — including immediately starting work on the next phase if the
|
||
current one is done
|
||
- Pick between two roughly equivalent implementations; justify the choice in
|
||
the commit message
|
||
- Refactor small amounts of surrounding code when genuinely needed to land a
|
||
change cleanly (but not "while I'm here" scope creep)
|
||
- Run the test suite, build the project, commit to main with co-author
|
||
attribution
|
||
- Add diagnostic logging when you need evidence, then strip it when the
|
||
evidence is in hand
|
||
- Spawn subagents for bounded implementation chunks (see Subagent policy)
|
||
|
||
Before claiming a phase or sub-step is done: run `dotnet build` and
|
||
`dotnet test` green, commit with a message that explains the "why", update
|
||
memory if there's a durable lesson, update the roadmap's "shipped" table if
|
||
a phase just landed, and move to the next todo item.
|
||
|
||
**If you catch yourself about to ask "should I continue?", the answer is
|
||
always yes — keep going.** The single exception is visual verification;
|
||
otherwise, act.
|
||
|
||
## Development workflow: decompile → verify → port
|
||
|
||
**This is the mandatory workflow for implementing ANY AC-specific behavior.**
|
||
The triangle-boundary Z bug cost 5 failed fix attempts from guessing.
|
||
The animation frame-swap bug cost 4 failed attempts. Every time we
|
||
checked the decompiled code first, we got it right on the first try.
|
||
|
||
### For each new feature or bug fix:
|
||
|
||
1. **DECOMPILE FIRST.** Before writing any AC-specific code, find the
|
||
matching function in the decompiled client (`docs/research/decompiled/`)
|
||
or decompile a new region using `tools/decompile_acclient.py`. Use
|
||
the function map at `docs/research/acclient_function_map.md` to find
|
||
known functions. If the function isn't mapped yet, search by
|
||
characteristic constants (motion commands, magic numbers, string
|
||
literals).
|
||
|
||
2. **CROSS-REFERENCE.** Check the decompiled code against ACE's C# port
|
||
(`references/ACE/Source/ACE.Server/Physics/`) and ACME's
|
||
`ClientReference.cs`. The decompiled code is ground truth; ACE and
|
||
ACME are interpretation aids. If they disagree, the decompiled code
|
||
wins.
|
||
|
||
3. **WRITE PSEUDOCODE.** Translate the decompiled C into readable
|
||
pseudocode before porting to C#. Save it in
|
||
`docs/research/*_pseudocode.md` for future reference. This step
|
||
catches misinterpretations before they become bugs.
|
||
|
||
4. **PORT FAITHFULLY.** Translate the pseudocode to C# line-by-line.
|
||
Use the same variable names, the same control flow, the same boundary
|
||
conditions. Do not "improve" or "simplify" the algorithm — the retail
|
||
client's code works; our job is to match it.
|
||
|
||
5. **CONFORMANCE TEST.** Write tests that verify our port matches the
|
||
decompiled behavior. Use golden values from the decompiled code or
|
||
from ACME's conformance tests. If the function touches terrain, port
|
||
the 4M-cell sweep from `TerrainConformanceTests.cs`.
|
||
|
||
6. **INTEGRATE SURGICALLY.** When wiring ported code into the renderer
|
||
or game loop, change the MINIMUM necessary. Keep existing working
|
||
transform pipelines, only replace the specific computation. The
|
||
animation sequencer integration proved this: replacing the slerp
|
||
source was safe; replacing the entire transform composition broke
|
||
everything.
|
||
|
||
### What NOT to do:
|
||
|
||
- **Do not guess** at AC-specific algorithms, formulas, constants, wire
|
||
formats, or coordinate conventions. Ever.
|
||
- **Do not "fix" the decompiled code.** If the retail client does
|
||
something that looks wrong, it's probably right. Verify before
|
||
changing.
|
||
- **Do not skip the pseudocode step.** The frame-swap bug was caused by
|
||
misreading the decompiled C directly into C# without an intermediate
|
||
translation.
|
||
- **Do not integrate via subagent** unless the subagent has the full
|
||
context of the existing code it's modifying. The first animation
|
||
sequencer integration was done by a subagent that didn't understand
|
||
the transform pipeline — it broke everything.
|
||
|
||
### Phase completion checklist:
|
||
|
||
Before marking any phase as done:
|
||
|
||
- [ ] Every AC-specific algorithm has a decompiled reference cited in
|
||
comments (function address + chunk file)
|
||
- [ ] Conformance tests exist for the critical paths
|
||
- [ ] The code was cross-referenced against at least 2 reference repos
|
||
- [ ] `dotnet build` green, `dotnet test` green
|
||
- [ ] Visual verification by the user (if applicable)
|
||
- [ ] Roadmap updated
|
||
- [ ] Memory updated if there's a durable lesson
|
||
|
||
## Subagent policy
|
||
|
||
Subagents are the primary tool for saving parent-context and keeping one
|
||
session productive across many phases. Use them liberally for:
|
||
|
||
- Bounded implementation chunks with a clear spec (one file, one test suite,
|
||
a targeted refactor)
|
||
- Parallel independent tasks with no shared state
|
||
- Research that would otherwise fill the parent context with file reads
|
||
|
||
**Model selection:**
|
||
|
||
- **Default: Sonnet.** Use Sonnet for all execution work — implementers,
|
||
research agents, spec-following work, test writing, refactors, repeated
|
||
patterns. Sonnet is the right cost/context/capability tradeoff for this
|
||
codebase and has been validated on every phase since Phase 2a. Do not
|
||
reach for Opus unless you have a specific reason.
|
||
- **Opus only for load-bearing quality review** — code review of a phase
|
||
boundary, a design that must be right the first time, a gnarly
|
||
cross-system refactor. "This feels hard" is not enough; specify why it
|
||
needs Opus in the task description.
|
||
- Never use Haiku for acdream work unless the task is literally checking
|
||
whether another process is alive.
|
||
|
||
**Prompt discipline:** when dispatching a subagent, include the relevant
|
||
spec path, the files it should read, the acceptance criteria (build + test
|
||
green), and the commit message style. Subagents inherit CLAUDE.md so they
|
||
follow the same rules.
|
||
|
||
## Roadmap discipline
|
||
|
||
acdream's plan lives in two files committed to the repo:
|
||
|
||
- **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap. Single
|
||
source of truth for what's shipped, what's next, and the agreed order.
|
||
When you're about to pick up new work, read this first. When you ship a
|
||
phase or sub-step, move it from "ahead" to "shipped" in the same commit
|
||
that lands the work (or the very next commit).
|
||
|
||
- **`docs/superpowers/specs/*.md`** — per-phase detailed implementation
|
||
specs. Each active phase has one. When you're about to write code for a
|
||
named phase, read its spec, follow its component boundaries, and match its
|
||
acceptance criteria. Do not drift from the spec without explicit user
|
||
approval.
|
||
|
||
**Rules:**
|
||
|
||
1. Before starting a new phase or sub-piece, re-read the roadmap and the
|
||
relevant spec. State which phase you're on in the first action you take.
|
||
|
||
2. When reality and the plan diverge — the user observes a bug that doesn't
|
||
fit any existing phase, a technical discovery makes a phase description
|
||
wrong, a sub-piece turns out to be larger than expected — **pause and
|
||
brainstorm** with the `superpowers:brainstorming` skill before writing
|
||
code. Update the roadmap in the same session.
|
||
|
||
3. When shipping a phase, update the roadmap's "shipped" table and commit
|
||
the update in the same commit as (or immediately after) the
|
||
implementation commit.
|
||
|
||
4. Do not invent new phase numbers / letters on the fly. If you need a new
|
||
phase, add it to the roadmap first with the user, then reference it by
|
||
its assigned identifier. "Phase 11" and "Phase 9.3" conjured
|
||
mid-sentence are process smells — they mean the plan got out of sync
|
||
with the work.
|
||
|
||
5. If a single session ends up shipping work that spans multiple roadmap
|
||
phases, that's fine, but each commit message should name the phase it
|
||
belongs to (e.g. `feat(core): Phase A.1 — streaming region`).
|
||
|
||
The roadmap is not sacred — it changes. It IS the source of truth at any
|
||
given moment. When it's wrong, fix it. When it's right, follow it.
|
||
|
||
## Reference repos: check ALL FOUR, not just one
|
||
|
||
When researching a protocol detail, dat format, rendering algorithm, or
|
||
any "how does AC do X" question, **check all four of the vendored
|
||
references in `references/`** before committing to an approach. Do not
|
||
settle on the first hit and move on — cross-reference at least two of
|
||
these, ideally all four:
|
||
|
||
- **`references/ACE/`** — ACEmulator server. Authority on the wire
|
||
protocol (packet framing, ISAAC, game message opcodes, serialization
|
||
order). The things a server has to know to parse and produce bytes.
|
||
- **`references/ACViewer/`** — MonoGame-based dat viewer that actually
|
||
renders characters + world. Authority on the client-side visual
|
||
pipeline: ObjDesc application, palette overlays, texture decoding
|
||
for the palette-indexed formats. See
|
||
`ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical
|
||
subpalette overlay algorithm.
|
||
- **`references/WorldBuilder/`** — C# + Silk.NET dat editor. Exact-stack
|
||
match to acdream for rendering approaches: terrain blending, texture
|
||
atlases, shader patterns. Most useful for "how do I do this GL thing
|
||
with Silk.NET on net10 idiomatically?" Less useful for protocol or
|
||
character appearance (dat editor, not game client).
|
||
- **`references/Chorizite.ACProtocol/`** — clean-room C# protocol
|
||
library generated from a protocol XML description. Useful sanity check
|
||
on field order, packed-dword conventions, type-prefix handling. The
|
||
generated Types/*.cs files have accurate field comments (e.g. "If
|
||
it is 0, it defaults to 256*8") that ACE's server-side code doesn't.
|
||
- **`references/holtburger/`** — **Almost-complete Rust TUI AC client.**
|
||
Not just a crate or a handshake reference: it's a full client that
|
||
logs in, plays the game, sends/receives chat, handles combat, and
|
||
renders state in a terminal. **This is acdream's most authoritative
|
||
reference for client-side behavior** — anything about how a client
|
||
is *supposed* to talk to the server lives here. Specifically:
|
||
- Handshake / login flow including all the post-EnterWorld
|
||
messages retail clients send (LoginComplete, ack pump,
|
||
DDDInterrogation responses, etc).
|
||
- The proper ACK_SEQUENCE pattern (every received packet with
|
||
sequence > 0 gets an ack queued back; not periodic).
|
||
- Outbound game-action message construction with sequence
|
||
numbering.
|
||
- Message routing and session lifecycle.
|
||
Look here FIRST when implementing anything in `WorldSession` or
|
||
the message-builder layer. ACE shows what the server expects;
|
||
holtburger shows what a real client actually sends.
|
||
|
||
- **`references/AC2D/`** — **C++ AC client emulator.** Oldest reference,
|
||
fixed-function OpenGL, but has the **real AC terrain split formula**
|
||
(`FSplitNESW` with constants `0x0CCAC033`, `0x421BE3BD`, `0x6C1AC587`,
|
||
`0x519B8F25`) which differs from WorldBuilder's physics-path formula.
|
||
Also has the complete `0xF61C` movement packet format with flag bits
|
||
and the `stMoveInfo` sequence counters. Key lesson from AC2D: it does
|
||
NOT do client-side terrain Z — it sends movement keys to the server
|
||
and uses the server's authoritative Z. See
|
||
`docs/research/2026-04-12-movement-deep-dive.md` for the full analysis.
|
||
|
||
Pattern: when you encounter an unknown behavior, grep all four for the
|
||
relevant term, read each hit, and compose a multi-source understanding
|
||
BEFORE writing acdream code. A single reference can be misleading; the
|
||
intersection of all four is almost always the truth. The user has
|
||
repeatedly had to remind me about this when I narrowly searched one ref
|
||
and missed obvious answers in another.
|
||
|
||
### Reference hierarchy by domain
|
||
|
||
**NEVER GUESS an algorithm, formula, constant, wire format, or coordinate
|
||
convention.** Every AC-specific behavior has a reference implementation in
|
||
one of the repos below. If you find yourself writing AC-specific code
|
||
without having read the matching reference first, STOP and read it. The
|
||
triangle-boundary Z bug cost 5 failed fix attempts because we guessed
|
||
instead of checking ACME's `ClientReference.cs` — which had the exact
|
||
decompiled client code and would have fixed it in minutes.
|
||
|
||
**The rule: read the reference FIRST, write code SECOND. Always.**
|
||
|
||
| Domain | Primary Oracle | Secondary | Notes |
|
||
|--------|---------------|-----------|-------|
|
||
| **Terrain** (split direction, height sampling, palCode, vertex position, normals) | **ACME `ClientReference.cs`** — decompiled retail client with exact offsets | ACME `TerrainGeometryGenerator.cs` (matches the mesh index buffer) | WorldBuilder original is SUPERSEDED for terrain algorithms. AC2D confirms the same formula. |
|
||
| **Terrain blending** (texture atlas, alpha masks, road overlays) | **ACME `LandSurfaceManager.cs`** | WorldBuilder original `LandSurfaceManager.cs` (same code, less tested) | Both use the same TexMerge pipeline. ACME has conformance tests. |
|
||
| **GfxObj / Setup rendering** (mesh extraction, multi-part assembly, ObjDesc) | **ACME `StaticObjectManager.cs`** — includes CreaturePalette, GfxObjRemapping, HiddenParts | ACViewer `Render/` namespace | ACME has the complete creature appearance pipeline in one file. |
|
||
| **Texture decoding** (INDEX16, P8, DXT, BGRA, alpha) | **ACME `TextureHelpers.cs`** | ACViewer `Render/TextureCache.cs` (palette overlay = `IndexToColor`) | For subpalette overlay specifically, ACViewer's `IndexToColor` is the canonical algorithm. |
|
||
| **EnvCell / dungeon rendering** (cell geometry, portal visibility, collision mesh) | **ACME `EnvCellManager.cs`** — portal traversal, mixed landblock detection, collision cache | ACViewer `Physics/Common/EnvCell.cs` | ACME is significantly more complete than original WorldBuilder for dungeons. |
|
||
| **Network protocol** (wire format, packet framing, fragment assembly, ISAAC) | **holtburger** `crates/holtburger-session/` | AC2D `cNetwork.cpp` (simpler, good for cross-check) | ACE shows the server side; holtburger + AC2D show the client side. |
|
||
| **Client behavior** (what to send when, login flow, ack pattern, keepalive) | **holtburger** `crates/holtburger-core/src/client/` | AC2D `cNetwork.cpp` + `cInterface.cpp` | holtburger is the most complete; AC2D is simpler but confirmed working. |
|
||
| **Movement** (MoveToState format, AutonomousPosition, sequence counters, speed) | **holtburger** `client/movement/` | AC2D `cNetwork.cpp:2592-2664` (0xF61C format) | See `docs/research/2026-04-12-movement-deep-dive.md` for the full cross-reference. |
|
||
| **Server expectations** (what ACE accepts/rejects, validation thresholds) | **ACE** `Source/ACE.Server/Network/` | — | Only ACE knows what the server actually validates. |
|
||
| **Silk.NET / .NET 10 idioms** (GL calls, shader setup, VAO patterns) | **WorldBuilder original** | ACME (same stack) | Both use the same backend; original has cleaner isolated examples. |
|
||
| **Protocol field order** (packed dwords, type prefixes, flag enums) | **Chorizite.ACProtocol** `Types/*.cs` | holtburger (cross-check) | Generated from protocol XML; has accurate field comments. |
|
||
|
||
### ACME key files quick reference
|
||
|
||
These are the files you should open FIRST when working on any rendering
|
||
or dat-interpretation task:
|
||
|
||
- **`WorldBuilder.Tests/ClientReference.cs`** — decompiled retail AC
|
||
client C# port. `IsSWtoNECut`, `GetPalCode`, `GetVertexHeight`,
|
||
`GetVertexPosition`. **The ground truth.** If your code disagrees
|
||
with this file, your code is wrong.
|
||
- **`WorldBuilder.Tests/TerrainConformanceTests.cs`** — 4M+ cell sweep
|
||
proving ACME matches retail. Port these into acdream's test suite for
|
||
any algorithm you touch.
|
||
- **`StaticObjectManager.cs`** — GfxObj+Setup+CreaturePalette pipeline.
|
||
- **`EnvCellManager.cs`** — dungeon cells + portal visibility.
|
||
- **`TerrainGeometryGenerator.cs`** — `GetHeight()`, `GetNormal()`,
|
||
`CalculateSplitDirection()` matching the mesh index buffer.
|
||
- **`TextureHelpers.cs`** — INDEX16, BGRA, DXT decode helpers.
|
||
|
||
### holtburger key files quick reference
|
||
|
||
These are the files you should open FIRST when working on any networking
|
||
or client-behavior task:
|
||
|
||
- **`client/movement/system.rs`** — the movement state machine (when to
|
||
send MoveToState vs AutonomousPosition, deduplication logic).
|
||
- **`client/movement/actions.rs`** — MoveToState, AutonomousPosition,
|
||
Jump wire format builders.
|
||
- **`client/movement/types.rs`** — RawMotionState packed format with
|
||
all flag bits documented.
|
||
- **`session/send.rs`** — packet construction, checksum, ISAAC, ACK
|
||
piggybacking.
|
||
- **`client/messages.rs`** — post-login message handlers (PlayerCreate
|
||
→ LoginComplete, DddInterrogation → response, PlayerTeleport).
|
||
- **`spatial/physics.rs`** — dead-reckoning solver (how the client
|
||
advances position between server updates).
|