# 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 **named retail decomp** at `docs/research/named-retail/` — Sept 2013 EoR build PDB (18,366 named functions, 5,371 named struct/class types) + Binary Ninja pseudo-C with 99.6% function-name recovery + verbatim retail header struct definitions. The older Ghidra `FUN_xxx` chunks under `docs/research/decompiled/` (22,225 functions, 688K lines) remain a fallback for chunk-by-chunk address-range navigation. **Grep `named-retail/acclient_2013_pseudo_c.txt` by `class::method` BEFORE decompiling fresh.** 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/`. **UI strategy:** three-layer split — swappable backend (ImGui.NET + `Silk.NET.OpenGL.Extensions.ImGui` for Phase D.2a, custom retail-look toolkit for D.2b later) / stable `AcDream.UI.Abstractions` layer (ViewModels + Commands + `IPanel` / `IPanelRenderer`) / unchanged game state. **As of Phase I (2026-04-25), ImGui hosts every dev/debug panel** — Vitals, Chat, Debug. The previous custom-StbTrueTypeSharp `DebugOverlay` was deleted in I.2; `TextRenderer` + `BitmapFont` are kept alive specifically for the future world-space HUD (D.6 — damage floaters, name plates) where ImGui can't reach into the 3D scene. D.2b remains the long-term retail-look path (panels reskinned one at a time using dat assets); ImGui persists forever as the `ACDREAM_DEVTOOLS=1` overlay. **All plugin-facing UI targets `AcDream.UI.Abstractions` — never import a backend namespace from a panel.** Full design: `docs/plans/2026-04-24-ui-framework.md`. Memory cribs: `memory/project_ui_architecture.md` (architecture), `memory/project_chat_pipeline.md` (chat pipeline as of Phase I), `memory/project_input_pipeline.md` (input pipeline as of Phase K). **Input pipeline:** `src/AcDream.UI.Abstractions/Input/` (action enum, `KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope stack + modal capture for rebind UX) + `src/AcDream.App/Input/` (Silk.NET adapters). Retail-default keymap loaded from `%LOCALAPPDATA%\acdream\keybinds.json` at startup (falls back to `KeyBindings.RetailDefaults()` matching `docs/research/named-retail/retail-default.keymap.txt`). The Settings panel (F11 / View → Settings) lets users remap any action via click-to-rebind. As of Phase K (2026-04-26), ALL keyboard / mouse input flows through the dispatcher — no IsKeyPressed polling outside the per-frame movement queries. ## 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. ## Communication style The user is a strong systems / C# / network programmer but **less practiced at 3D math, physics, graphics, and animation**. They want to learn — they're not asking for dumbed-down content, but for explanations that build understanding alongside the work. When discussing 3D / physics / graphics / animation / dat-format / protocol-internals topics: - **Name the concept in plain language first**, then introduce the term of art. "The angle of a slope (we call its straight-up component `Normal.Z`)" rather than dropping `normal.Z = cos θ` with no anchor. - **Give units**: degrees, meters, cm — NOT raw floats. "FloorZ ≈ 0.66 means slopes up to about 49° are walkable" rather than "FloorZ = 0.66417414f". Floats are for the code; English is for the conversation. - **Use analogies for spatial concepts** when they fit. A BSP tree is "a way of slicing space into nested rooms"; a contact plane is "the imaginary floor under the player's feet"; a sphere sweep is "rolling a ball forward through space and stopping it on contact"; a cross product is "the direction perpendicular to two arrows"; a dot product is "how aligned two arrows are (1 = same, 0 = perpendicular, -1 = opposite)". - **Don't pile on multiple new concepts in one paragraph.** If a problem touches step-up AND step-down AND edge-slide AND walkable-polygon tracking, walk through them one at a time, each with what it does and why it exists. - **Show the math when it matters, but explain it.** Don't just drop a formula and move on; tag it with "what this means geometrically". - **Use frame-by-frame walk-throughs** for control-flow-heavy physics: "frame N: player here, lands. Frame N+1: state checks…" beats a function-call trace for understanding what's happening in motion. - **Flag terms of art** the first time they appear in a session, even if they're sprinkled through code comments. "Broadphase", "BSP", "step-up", "ContactPlane", "ValidateWalkable" — they earn their meaning the first time you spell it out. The goal is collaborative learning. Don't simplify the content; just make sure every term and number is grounded so the user can keep up and build intuition over time. ## Development workflow: grep named → 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. **Now we have named retail symbols too — Step 0 cuts most lookups from 30 minutes to 5 seconds. And as of 2026-04-30, when "what does retail actually DO at runtime?" is the question and decomp alone isn't enough, attach cdb to a live retail client (Step -1).** ### For each new feature or bug fix: -1. **ATTACH cdb TO RETAIL (when behavior is the question, not code).** For "what does retail actually DO frame-by-frame?" questions — wedges, weird animation flicker, geometry-specific bugs, anything where the decomp is correct but it's not clear how it produces the visible behavior — **don't guess; attach the Windows debugger to a live retail client and trace it.** See "Retail debugger toolchain" below for setup. We discovered the steep-roof wedge had a 30Hz physics-tick cause this way; would have taken weeks of guessing without the trace. 0. **GREP NAMED FIRST.** Before any decompilation work, search `docs/research/named-retail/acclient_2013_pseudo_c.txt` by `class::method` name. 99.6% of functions have real names from the Sept 2013 EoR build PDB. `docs/research/named-retail/acclient.h` has every retail struct verbatim. `docs/research/named-retail/symbols.json` is greppable by name or address (regenerate via `py tools/pdb-extract/pdb_extract.py refs/acclient.pdb`). Only fall back to Step 1 below if the named pseudo-C lacks a function (rare — covers only the obfuscated/packed minority). 1. **DECOMPILE FIRST (fallback).** Only when grep-named-first returned nothing. Find the matching function in the older Ghidra chunks at `docs/research/decompiled/` or decompile a new region using `tools/decompile_acclient.py`. Use the function map at `docs/research/acclient_function_map.md` (cross-port index) + `docs/research/named-retail/symbols.json` (raw PDB names) 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. **The named retail decomp has the answer for almost everything; guessing is no longer a recoverable error, it's negligence.** If you can't find it in `docs/research/named-retail/`, file a research note and ASK before writing. - **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 (named symbol + address from `named-retail/symbols.json`, OR function address + chunk file from older `decompiled/` chunks) - [ ] 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 ## Retail debugger toolchain (live runtime trace) **When the question is "what does retail actually DO frame-by-frame?"** the decomp alone is often not enough — code paths interact with state (LastKnownContactPlane, transient flags, accumulated counters) in ways that aren't obvious from reading. As of 2026-04-30 we have a working toolchain to attach Windows' console debugger (cdb.exe) to a live retail acclient.exe with full PDB symbols and capture state at any breakpoint. **Use this when guessing has failed twice in a row.** ### What we have - **Matching binary**: `C:\Turbine\Asheron's Call\acclient.exe` v11.4186 (linker timestamp `2013-09-06 00:17:42 UTC`, CodeView GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`). Pairs exactly with our `refs/acclient.pdb`. - **Debugger**: `cdb.exe` (console WinDbg) at `C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe`. Install via Microsoft Store WinDbg (~50 MB). 32-bit version is required for acclient.exe. - **PDB**: `refs/acclient.pdb` (29 MB, Sept 2013 EoR build). 18,366 named functions + 5,371 named struct types resolve. - **Symbol verifier**: `tools/pdb-extract/check_exe_pdb.py ` reads any acclient.exe and prints whether it pairs with our PDB (`MATCH` / `MISMATCH (expected GUID = ...)`). Always run this on a candidate binary BEFORE attaching. - **PDB metadata dumper**: `tools/pdb-extract/dump_pdb_info.py refs/acclient.pdb` prints the PDB's expected timestamp + GUID + age. Use to figure out which build to look for if the chain ever breaks. ### Workflow 1. **Verify the binary matches the PDB:** ```bash py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe" ``` Expect: `=== MATCH: this exe pairs with our acclient.pdb ===` 2. **Have the user launch retail client** and connect to local ACE. Retail must already be in-world before attaching. 3. **Write a `.cdb` script** that arms breakpoints with non-blocking actions (count + log + `gc`). Pattern: ``` .logopen .sympath C:\Users\erikn\source\repos\acdream\refs .symopt+ 0x40 .reload /f acclient.exe r $t0 = 0 bp acclient!CTransition::transitional_insert "r $t0 = @$t0 + 1; .if (@$t0 % 5000 == 0) { .printf \"...\" }; .if (@$t0 >= 30000) { qd } .else { gc }" bp acclient!OBJECTINFO::kill_velocity "r $t1 = @$t1 + 1; gc" ... g ``` `gc` = "go conditional" (continue without breaking). Auto-detach via `qd` after a hit-count threshold to avoid manual cleanup. 4. **Launch cdb in the background** via a PowerShell wrapper: ```powershell & "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" ` -pn acclient.exe -cf