diff --git a/AGENTS.md b/AGENTS.md index d27a33d0..f10f0e2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,31 +1,1158 @@ -# Agent Instructions + -`CLAUDE.md` is the source of truth for this repository. +# acdream — project instructions for agents -Before doing project work, read `CLAUDE.md` and follow it. If anything in this -file appears to conflict with `CLAUDE.md`, treat `CLAUDE.md` as authoritative -and update this file rather than carrying a second version of the rules. +## Goal -## Codex Notes +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. -- Keep this file as a thin bridge for agent tooling that discovers - `AGENTS.md`. Do not duplicate the full project state, roadmap, memory - protocol, launch workflow, or domain reference tables here. -- The project goal is `acdream`: a modern open-source C#/.NET 10 Asheron's - Call client whose behavior faithfully tracks the retail client while using a - modern architecture. -- Align all code and planning work with the architecture document named in - `CLAUDE.md`, the current milestones/roadmap docs, and the relevant memory - digests before touching a subsystem. -- Treat `GameWindow.cs` as runtime wiring. New substantial behavior belongs in - dedicated controllers, sinks, orchestrators, or Core/UI abstractions according - to the layer rules in `CLAUDE.md`. -- For AC-specific algorithms, wire formats, constants, coordinates, rendering, - physics, movement, UI, audio, chat, plugin behavior, or dat handling, read the - references named in `CLAUDE.md` before implementing. Do not guess. -- Fix root causes. Do not ship workaround guards, retry loops, grace periods, - suppressions, or symptom masks without explicit approval. -- Continue autonomously through implementation, build/test verification, docs, - memory updates, and commits when appropriate. The main reason to stop is when - visual confirmation from the user is the actual acceptance test. +**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. + +**WorldBuilder code lives in our tree.** Phase O extracted ~33 WB files +(~7.7K LOC) into our own namespaces and dropped the two external project +references. `DatCollection` is the **only** dat reader in process — +`DefaultDatReaderWriter` is gone. `references/WorldBuilder/` remains +in-tree as a read-reference (MIT-licensed; grep it freely), but nothing +in `src/AcDream.*` references it as a project dependency. + +**Where the extracted code lives (post-Phase O):** +- `src/AcDream.Core/Rendering/Wb/` — pure dat/mesh helpers (5 files, + ~782 LOC): `TerrainUtils`, `TerrainEntry`, `RegionInfo`, + `SceneryHelpers`, `TextureHelpers`. No GL dependency; safe to use + from Core. +- `src/AcDream.App/Rendering/Wb/` — GL infrastructure + mesh pipeline + (~27 files, ~7K LOC): `ObjectMeshManager`, `WbMeshAdapter`, + `WbDrawDispatcher`, `LandblockSpawnAdapter`, `EntitySpawnAdapter`, + `TextureCache`, `GlobalMeshBuffer`, shader infrastructure, and the + EnvCell/portal/scenery/terrain-blending pipeline classes. + +**Modern rendering path is MANDATORY** as of the N.5 ship amendment. +`WbFoundationFlag`, `InstancedMeshRenderer`, and `StaticMeshRenderer` +are deleted. Missing `GL_ARB_bindless_texture` or +`GL_ARB_shader_draw_parameters` throws `NotSupportedException` at +startup. There is no legacy fallback. Engineering cribs (WbMeshAdapter +seams, N.5 SSBO layout, translucency model, gotchas) live in +`memory/reference_modern_rendering_pipeline.md`. + +Before re-implementing any AC-specific rendering or dat-handling +algorithm, **read `docs/architecture/worldbuilder-inventory.md` FIRST**. +The inventory describes what we extracted (now in our tree) and what we +still write ourselves. Re-porting from retail decomp when we already +have a tested port is how subtle bugs (the scenery edge-vertex bug, the +triangle-Z bug) keep slipping in. Retail decomp remains the oracle for +network, physics, animation, movement, UI, plugin, audio, chat — see +the inventory doc's 🔴 list. + +**Execution model:** the active source of truth is the **milestones doc** +(`docs/plans/2026-05-12-milestones.md`) for "what are we building right +now" and the **strategic roadmap** (`docs/plans/2026-04-11-roadmap.md`) +for the per-phase ledger of what's shipped, what's in flight, and what +comes next. **Ignore the old "R1→R8" sequence** — it was an early refactor +sketch that no longer matches reality (see the "Roadmap Model" section in +`docs/architecture/acdream-architecture.md`). Per-phase detailed specs +live under `docs/superpowers/specs/`. + +The codebase is organized by layer (see architecture doc + the **Code +Structure Rules** section below). Plans live in `docs/plans/`, +research in `docs/research/`, persistent project memory in `memory/` +and `~/.claude/projects/.../memory/` (the latter is browsable in +Obsidian via the `claude-memory/` junction in the repo root; see +`memory/reference_obsidian_vault.md`). + +**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, 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`](docs/plans/2026-04-24-ui-framework.md). +Memory cribs: `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, ALL keyboard / mouse input flows +through the dispatcher — no IsKeyPressed polling outside the per-frame +movement queries. + +## Current state + +**Currently working toward: M2 — Kill a drudge** (equip sword → hit a drudge → damage +in chat → loot → inventory). **M1.5 — Indoor world feels right LANDED 2026-07-10**: +buildings/cellars/multi-floor inns + full dungeon round-trip (enter→navigate→exit) all +user-gated; #138 closed by the round-trip gate; #133/#137/#95/#79/#93/#80 CLOSED. M2 +first ports = `CombatMath.ComputeDamage` (F.3) + inventory panel (F.2) + combat anim +(L.1c) — see the M2 section in the milestones doc + `docs/research/2026-06-04-combat-math-deep-dive.md`. +**Carried post-M1.5, NOT blockers:** #145-residual (far-town teleport-OUT cascade — +capture-harness-first), #116 slide-response. **D.2b retail UI** parity track still +interleaves at the issue level (next: container-switching — `claude-memory/project_d2b_retail_ui.md`); +**R5 movement-manager arc DONE** (2026-07-05; carried #167, R6/TS-42); **Track MP** perf +side track at MP0. Keep this paragraph ≤6 lines + pointers — detail in the docs below, NOT here. + +For canonical state, read in this order: +- [`docs/plans/2026-05-12-milestones.md`](docs/plans/2026-05-12-milestones.md) — milestone targets + freeze list per milestone +- [`docs/plans/2026-04-11-roadmap.md`](docs/plans/2026-04-11-roadmap.md) — what's shipped, what's in flight, what's next +- [`docs/ISSUES.md`](docs/ISSUES.md) — open + recently closed bugs (tactical) +- [`docs/architecture/retail-divergence-register.md`](docs/architecture/retail-divergence-register.md) — every known acdream-vs-retail deviation (see the register rules in the workflow section) + +**Domain entry points (START HERE before domain work):** +- `claude-memory/project_render_pipeline_digest.md` — indoor render / doorway-FLAP / portal flood SSOT, with the DO-NOT-RETRY table +- `claude-memory/project_physics_collision_digest.md` — physics / collision / cell-membership SSOT, with the DO-NOT-RETRY table + +For engineering reference (read on demand, not at session start): +- `memory/reference_modern_rendering_pipeline.md` — N.4/N.5 bindless+MDI dispatch, WbMeshAdapter/WbDrawDispatcher, translucency model +- `memory/reference_two_tier_streaming.md` — Phase A.5 streaming architecture +- `memory/reference_indoor_cell_tracking.md` — Phase 2 + A4 portal-based cell tracking + multi-cell BSP iteration +- `memory/reference_obsidian_vault.md` — Obsidian-as-memory-viewer setup + the memory-handling protocol +- `memory/reference_ghidra_projects.md` — Ghidra + GhidraMCP for acclient RE +- `memory/reference_repos.md` — what each `references/*` repo is for + +The memory dir also holds `feedback_*.md` lessons-learned (cross-cutting +patterns the project has agreed on) and `project_*.md` per-subsystem +cribs (chat pipeline, input pipeline, interaction pipeline, etc.). +See `memory/MEMORY.md` for the index. + +## Code Structure Rules + +These are the structural rules the project commits to. They are +**process rules** (where code goes, what depends on what), not style +rules (formatting / naming). They exist to keep the layer split honest +and to stop `GameWindow.cs` from continuing to grow into a 10k-line +god object. The full rationale + the extraction sequence we're +pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-structure.md). + +1. **No new substantial feature bodies in `GameWindow.cs`.** It is + already over 10,000 lines and owns runtime wiring. New runtime + work goes into a dedicated controller / sink / orchestrator class + in `src/AcDream.App/` (or deeper in `AcDream.Core` when it's pure + logic). Adding a handful of fields and a one-paragraph method to + wire an extracted class in is fine; adding a new ~200-line feature + directly is not. When in doubt, file a small follow-up extraction + as part of the change. + +2. **`AcDream.Core` must not depend on the window / GL / backend + projects, except via documented interop seams.** As of Phase O, + the only allowed seams are the extracted helpers in + `src/AcDream.Core/Rendering/Wb/` (`TerrainUtils`, `TerrainEntry`, + `RegionInfo`, `SceneryHelpers`, `TextureHelpers` — stateless, no GL). + The former `WorldBuilder.Shared` and `Chorizite.OpenGLSDLBackend.Lib` + project references are gone; their code now lives in our tree at those + paths. New Core code that needs a GL surface must define an interface + in Core and let `AcDream.App` implement it — never the reverse. If you + need to add a project reference to Core, the change must come with an + inventory-doc update explaining why. + +3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may + import `AcDream.UI.ImGui` or any backend namespace. ViewModels, + commands, and the `IPanel` / `IPanelRenderer` contract are the + surface; everything else is backend. This is what lets us swap + D.2a (ImGui) for D.2b (retail-look) later without rewriting + panels. + +4. **Startup environment variables enter through a typed options + object.** `AcDream.App.RuntimeOptions` is the single source of + truth for startup configuration. `Program.cs` reads the + environment once into `RuntimeOptions` and passes it into + `GameWindow`. Don't sprinkle `Environment.GetEnvironmentVariable` + reads through new code paths; add a field to `RuntimeOptions` and + pipe it through. + +5. **Runtime probes (diagnostic toggles) belong in diagnostic owner + classes.** Today `AcDream.Core.Physics.PhysicsDiagnostics` owns the + `ACDREAM_PROBE_*` family. The pattern: one static class per + subsystem, exposing typed bool/int properties read from env vars + once at startup (with optional runtime-toggleable counterparts for + the DebugPanel). Per-call-site `Environment.GetEnvironmentVariable` + reads in new code are a process smell — if a flag survives one + phase, promote it to a diagnostic owner. The dozens of existing + `ACDREAM_DUMP_*` reads scattered through `GameWindow` are tech + debt; do not add more. + +6. **Tests live in the project matching the layer under test.** Core + tests in `tests/AcDream.Core.Tests/`, UI tests in + `tests/AcDream.UI.Abstractions.Tests/`, network tests in + `tests/AcDream.Core.Net.Tests/`. App-layer tests (RuntimeOptions + parsing, etc.) belong in `tests/AcDream.App.Tests/`. When adding a + new test project, register it in `AcDream.slnx`. + +## How to operate + +**Memory — read the digests before domain work.** Durable project knowledge +lives in `claude-memory/` (the auto-loaded index is `MEMORY.md`). Before starting +work in a domain that has a **digest**, read it first: `project_render_pipeline_digest.md` +(indoor render / doorway FLAP) and `project_physics_collision_digest.md` +(physics / collision / membership). Each digest is current-truth-on-top +plus a DO-NOT-RETRY table — it supersedes dated banners. The memory-handling +protocol (distill-don't-journal, the digest pattern, tags, recall + capture) +is in `reference_obsidian_vault.md`. **Do NOT add new dated status banners to +this file — update the relevant digest (or the Current state pointer list) +instead.** Obsidian (auto-started in the main repo via a SessionStart hook) is +the live search / graph / tag lens over `claude-memory/` through the +`mcp__obsidian__*` tools when it's running; the files read and write the same +with it closed. + +**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 (milestones doc + strategic roadmap), 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. + +**No workarounds without explicit approval.** When you spot a bug or +encounter a behavioral mismatch, fix the underlying cause — do not ship a +band-aid, suppression flag, grace period, retry loop, or any other "make +the symptom go away" shortcut, unless the user has explicitly approved +that shape OR you are building a NEW feature with a different design. +This rule exists because every workaround creates architectural debt that +masks the real issue, makes future refactors harder, and erodes the +codebase's retail-faithfulness. Examples of disallowed shortcuts: an +`if (problematicState) return early` guard at the symptom site instead of +investigating why the state happened; a timer-based "settle period" to +hide a race; a flag like `_suppressXDuringY` to mask a wire-level mistake; +a `try/catch` swallowing an exception that signals a real problem. If you +notice a fix is starting to look like a workaround mid-implementation, +stop, file the proper investigation as an issue with full reproduction +notes, and either (a) ask the user before shipping the workaround, or +(b) invest the time to fix the root cause. The user has explicitly +authorized "spend more time, get it right" over "ship a shortcut and +file the cleanup." Quote them: "we should have no workarounds unless I +say so or we want a different feature." + +**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 +- **The request is an investigation, audit, analysis, review, or "report-only"** + — no edits, no writes, no diagnostic code drops until you've delivered the + report and the user explicitly approves a fix. Use the `/investigate` skill + (`.claude/skills/investigate/SKILL.md`) to enter this mode cleanly. +- **A referenced commit, file path, branch, or doc doesn't exist** where the + user said it would. Ask one short question; don't go hunting across branches + or worktrees. A 1-line clarification beats 30 minutes of wrong-branch + exploration. + +**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. **You pick what comes next** per the Milestone + discipline section — never present the user a menu like "should we do X + or Y?" or ask "what next?". Just choose and announce the choice in one + sentence. Work-order selection is the agent's job, not the user's. +- 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. 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. + +### The divergence register (mandatory bookkeeping) + +[`docs/architecture/retail-divergence-register.md`](docs/architecture/retail-divergence-register.md) +is the single auditable list of every KNOWN place acdream's runtime +behavior can deviate from retail (108 rows at creation, 2026-06-12: +intentional architecture / adaptation / approximation / stopgap / +unclear). Two rules, both binding on subagents too: + +1. **Any commit that introduces a deviation** (an adaptation, an + approximation, a stopgap, a "retail does X but we...") **adds its + register row IN THE SAME COMMIT.** Any commit that ports the retail + mechanism deletes the row in the same commit. A deviation found + without a row is a bug twice over. +2. **Any unexplained visual/physics symptom → scan the register BEFORE + instrumenting.** The "Risk if assumption breaks" column is written as + the symptom you'd observe (the #119 vanishing staircase, the #112 + transparent cottage, and the knife-edge flap all lived in rows of + this register's scope before they had names). + +The register holds one-line rows and pointers — detail lives at the +cited `file:line` and in the digests, never in the register itself. + +### 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. +- **Do not replace working retail-faithful logic with a modern redesign** + without explicit user approval. Two campaigns (267 min remote-entity + prediction+rubber-band replacing hard-snap; speculative shader edits in + the sky-fog work) had to be reverted in full because the redesign + regressed behavior the original port had right. When you see "I could + simplify this with X" on a retail-port, flag the tradeoff and ask + before deleting the existing path. Retail-faithful first; "cleaner" + second. + +### 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) +- [ ] Every retail deviation this phase introduced has a row in + `docs/architecture/retail-divergence-register.md` (and every + deviation it retired had its row deleted) +- [ ] 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. 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