Document the live-server loop: ACE at 127.0.0.1:9000, +Acdream test char, the canonical PowerShell launch command with env vars, the 3-5 s logout delay (exit 29 otherwise), diagnostic env vars, and the distinction between 'own view' vs 'retail observer view' when triaging motion bugs. This captures workflow that's been implicit across many sessions so any Claude instance picking up the project can launch against the live server on its first try instead of re-discovering the incantation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
23 KiB
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:
-
DECOMPILE FIRST. Before writing any AC-specific code, find the matching function in the decompiled client (
docs/research/decompiled/) or decompile a new region usingtools/decompile_acclient.py. Use the function map atdocs/research/acclient_function_map.mdto find known functions. If the function isn't mapped yet, search by characteristic constants (motion commands, magic numbers, string literals). -
CROSS-REFERENCE. Check the decompiled code against ACE's C# port (
references/ACE/Source/ACE.Server/Physics/) and ACME'sClientReference.cs. The decompiled code is ground truth; ACE and ACME are interpretation aids. If they disagree, the decompiled code wins. -
WRITE PSEUDOCODE. Translate the decompiled C into readable pseudocode before porting to C#. Save it in
docs/research/*_pseudocode.mdfor future reference. This step catches misinterpretations before they become bugs. -
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.
-
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. -
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 buildgreen,dotnet testgreen- 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:
-
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.
-
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:brainstormingskill before writing code. Update the roadmap in the same session. -
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.
-
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.
-
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.
Running the client against the live server
The user runs a local ACE (Asheron's Call Emulator) server on
127.0.0.1:9000 that stays up continuously. Iteration loop: launch the
acdream client, connect, test, close the window, rebuild, relaunch.
Connection details
| Setting | Value |
|---|---|
| Host / port | 127.0.0.1 / 9000 |
| Account | testaccount |
| Password | testpassword |
| Character | +Acdream (server guid 0x5000000A) — this is a + GM-marker character for dev testing |
| DAT directory | %USERPROFILE%\Documents\Asheron's Call\ (contains client_portal.dat, client_cell_1.dat, client_highres.dat, client_local_English.dat) |
Launch command
The canonical launch is via dotnet run with environment variables set.
Use PowerShell (Windows native) — bash struggles with the apostrophe in
"Asheron's Call":
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
$env:ACDREAM_LIVE = "1"
$env:ACDREAM_TEST_HOST = "127.0.0.1"
$env:ACDREAM_TEST_PORT = "9000"
$env:ACDREAM_TEST_USER = "testaccount"
$env:ACDREAM_TEST_PASS = "testpassword"
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug
Always pipe to a log file for post-run diagnostic grep:
2>&1 | Tee-Object -FilePath "launch.log". Run in the background via
the run_in_background: true parameter so the tool doesn't block waiting
for the window to close.
Logout-before-reconnect
ACE keeps your last session alive briefly after a disconnect. If you
relaunch the client within a few seconds of the last close, the handshake
fails with live: session failed: CharacterList not received and the
process exits with code 29. Wait ~3–5 seconds between launches, or explicitly
kill stale processes:
Get-Process -Name AcDream.App -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Seconds 3
# ... then launch ...
The user has repeatedly confirmed this — don't treat exit-29-after-rapid-relaunch as a code bug. It's a server-side session-cleanup delay.
Test character
+Acdream at server guid 0x5000000A. Starts at or near Holtburg. Has
basic stats; ACDREAM_RUN_SKILL / ACDREAM_JUMP_SKILL env vars (default
200) set the client-side skill value used by PlayerWeenie.InqRunRate
for local motion prediction. These are NOT synced to the server —
ACE's own character data is authoritative for broadcast motion. If you
see a speed/anim mismatch between local and observer views, the fix is
to sync the runSkill from ACE via UpdateMotion.ForwardSpeed echo (wired
via PlayerMovementController.ApplyServerRunRate) or from
PlayerDescription (0x0013).
Diagnostic env vars
ACDREAM_DUMP_MOTION=1— dump every inboundUpdateMotion(guid, stance, cmd, speed) + resultingSetCyclecall. Massive for remote- animation debugging.ACDREAM_STREAM_RADIUS=N— tune landblock visible-window radius (default 2 = 5×5).ACDREAM_NO_AUDIO=1— suppress OpenAL init for headless / driver- broken setups.
Visual verification workflow
- Make a code change.
dotnet build— must be green before launch.- Launch (background). Give it ~8 s to reach in-world state.
- User tests in the running client (moves, interacts, watches remote chars). Close window when done.
- Read
launch.logor the task output file for diagnostic lines. - Iterate.
Never launch without first confirming the build is green. A failed launch from a compile error wastes the user's testing time and can kill an already-running ACE session via the handshake race.
What the user watches for
- Own character in acdream: correct animation, correct speed, proper transitions (walk → run, run → stop, jump → land, strafe, turn).
- Remote toons from a retail client viewing acdream: the user often
runs the retail AC client in parallel and watches the acdream
+Acdreamcharacter from there. When they report "lagging forward" or "walking when I should be running" that's the retail observer view, not the acdream view. Distinguish these carefully — they're different code paths. - NPCs / monsters in acdream: animate their emotes, idle, attacks, stop transitions, and keep their visual position tracked smoothly between the 5–10 Hz UpdatePosition bursts (dead-reckoning).
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. SeeACViewer/Render/TextureCache.cs::IndexToColorfor 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 2568") 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
WorldSessionor 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 (FSplitNESWwith constants0x0CCAC033,0x421BE3BD,0x6C1AC587,0x519B8F25) which differs from WorldBuilder's physics-path formula. Also has the complete0xF61Cmovement packet format with flag bits and thestMoveInfosequence 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. Seedocs/research/2026-04-12-movement-deep-dive.mdfor 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).