diff --git a/.gitignore b/.gitignore index 5ac2beda..357fded9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,11 +26,8 @@ references/* # Claude Code session state .claude/ -# Superpowers brainstorm visual-companion scratch (mockups regenerate; not source) -/.superpowers/ launch.log launch-*.log -proveout*.log launch.utf8.log n4-verify*.log @@ -90,12 +87,3 @@ C[€- # Junction to Claude Code per-project memory (Obsidian vault visibility) claude-memory -studio-shots/ - -# MP1b acdream-bake output — user-machine artifact, never committed -# (docs/superpowers/plans/2026-07-05-mp1b-pak-and-bake.md, Task 5). -*.pak - -# session-local physics capture artifacts (worktree root) -/resolve-*.jsonl -/launch-*.log diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 51b38ef1..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,1162 +0,0 @@ - - -# acdream — project instructions for agents - -## 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. - -**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:** two coexisting presentation stacks over shared state, -ViewModels, events, and commands. ImGui.NET + -`Silk.NET.OpenGL.Extensions.ImGui` is the permanent -`ACDREAM_DEVTOOLS=1` developer stack using `IPanel`/`IPanelRenderer`. -Retail gameplay UI is the independent retained `UiHost`/`UiRoot` tree in -`AcDream.App/UI`, imported from LayoutDesc/DAT assets and bound by focused -controllers. The stable cross-stack seam is ViewModels/commands, not a backend -swap. `TextRenderer` + `BitmapFont` also serve D.6 world-space HUD elements -where ImGui cannot reach the 3D scene. Plugin gameplay UI uses the BCL-only -`AcDream.Plugin.Abstractions.IUiRegistry.AddMarkupPanel` contract; plugins -never import App or ImGui namespaces. Full design: -[`docs/plans/2026-04-24-ui-framework.md`](docs/plans/2026-04-24-ui-framework.md). -Memory cribs: `claude-memory/project_chat_pipeline.md` (chat pipeline as of -Phase I), `claude-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 - -**M3 — Cast a spell ACTIVE; automated implementation complete 2026-07-15.** Retail cast intent, -component/target gates, live enchantment wire, Magic-scoped input, spell bar, spellbook, component -book, effect indicators, shared main-panel switching, and positive/negative effects UI are implemented over the Step-9 projectile/DAT-effect -foundation. The 2026-07-20 R6 complete-root-Frame/object-workset cutover is automated-test complete; -three independent conformance/architecture/adversarial re-reviews are clean, the Release build succeeds -(the clean documentation audit exposes 17 pre-existing test-project warnings, #228), and the full suite -is 6,452 passed / 5 skipped. Its unattended seven-destination -connected input/portal/performance rebaseline also passes without an interactive desktop. Remaining -ownership cleanup and registered TS-50/TS-51 timing residuals stay carried. **Next:** connected R6 -locomotion/collision/projectile/teleport visual gate, then the final two-client -portal-out/materialization observer gate. **Carried:** #153, #116, R6 residuals, and the deferred -Modern Pipeline track (MP1b+). -Start magic work at `claude-memory/project_magic_ui_and_casting.md`; detailed state is below. - -Documentation entry point: [`docs/README.md`](docs/README.md). - -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 `claude-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:\Users\erikn\Downloads\acclient.exe` - v11.4186 (linker timestamp `2013-09-06 00:17:56 UTC`, - CodeView GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`). Pairs - exactly with our `refs/acclient.pdb`. The current - `C:\Turbine\Asheron's Call\acclient.exe` is a 2015 build with GUID - `08e25c14-e2a1-46d5-b056-92b2e43a7234` and must not be used for - this PDB/address map. -- **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:/Users/erikn/Downloads/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