acdream/CLAUDE.md
Erik af381ac6fb feat(net): Phase 4.9 — send ACK_SEQUENCE for every received server packet
Root cause of the still-purple-haze symptom AND the ACE-side
"Network Timeout" drop after ~60s. acdream was never sending
acknowledgement packets back to the server, so the server's
reliability layer saw a one-way stream and eventually dropped the
session. During the 60s window the player rendered to other clients
as the stationary purple loading haze (AC's "this client is in
portal-space transition" indicator).

Pattern ported from
references/holtburger/crates/holtburger-session/src/session/
{send.rs::send_ack, receive.rs::finalize_ordered_server_packet}.
The proper holtburger pattern is per-packet acks, NOT a periodic
heartbeat: every received server packet with sequence > 0 and no
ACK_SEQUENCE flag of its own gets a bare control packet sent back
with:

  PacketHeader {
    Flags    = ACK_SEQUENCE (0x4000),
    Sequence = current_client_sequence (= last issued, no increment),
    Id       = session client id,
  }
  Body = u32 little-endian server sequence being acked

Acks are cleartext control packets (no EncryptedChecksum) and
re-use the most recently issued client sequence rather than
consuming a new one — they aren't part of the reliable stream the
server tracks for retransmits.

Wired into ProcessDatagram so both Tick (post-InWorld) and PumpOnce
(during Connect/EnterWorld) trigger acks on every received non-ack
server packet.

Also (per user request) upgrades the CLAUDE.md description of the
holtburger reference repo from "Rust AC client crate" to "almost-
complete Rust TUI AC client — the most authoritative reference for
client-side behavior in the project, look here FIRST for anything
WorldSession or message-builder related." This was the third time
in two days I would have saved hours by checking holtburger first
instead of guessing at the protocol from ACE alone.

220 tests green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:42:41 +02:00

190 lines
9.3 KiB
Markdown

# acdream — project instructions for Claude
## Goal
Build **acdream**, a modern open-source C# .NET 10 Asheron's Call client. The
end state is a working client that:
- Loads the retail AC dat files and renders the world (terrain, static meshes,
dynamic entities, characters)
- Connects to an ACE server and plays as a character
- Exposes a **first-class plugin API** so players can write native scripts and
macros to automate gameplay — this is a core architectural requirement, not
a bolt-on
The codebase is organized by phase. Current phase state lives in memory
(`memory/project_phase_*_state.md`), current phase plans live in `docs/plans/`,
and the long-term vision lives in `memory/project_acdream.md`.
## How to operate
**You are the lead engineer on this project at all times. 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.
## 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.
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.