Campaign V migrates the renderer from OpenGL 4.3+extensions to a single
Vulkan 1.3 backend on Windows x64 and Linux x64, then deletes the GL path.
Motivation is compatibility and efficiency, not rescue: mandatory
GL_ARB_bindless_texture is the exact floor that parked Slice L (Mesa
D3D12/llvmpipe lack it) while Vulkan descriptor indexing is core, and
per-frame data can be written straight into mapped memory rather than
copied through BufferSubData.
V0 pins the contract every later slice codes against. Nothing consumes it
yet, so this commit changes no runtime behavior.
The seam is a minimal Vulkan-shaped RHI implemented FIRST on GL. That
ordering is the point: the twelve renderers then port one at a time under a
strict pixel gate on the still-shipping backend, so a divergence is
attributed to one slice instead of surfacing at a big-bang integration.
Duplicating renderers per backend was rejected because WbDrawDispatcher is
4,449 lines holding only ~62 GL call sites — the API surface is small and
the retail-fidelity CPU logic is large, and forking the latter is how subtle
regressions enter.
Contract highlights:
- GpuBindingModel pins set/binding numbers dual-legal for GL and Vulkan
GLSL. Storage bindings 0-8 keep today's shader numbering; UBOs move to
their own set, which resolves the binding=1 collision GL only tolerates
because it keeps SSBO and UBO tables separate.
- GpuRingAllocation is a ref struct replacing every per-frame
BufferSubData; the compiler forbids outliving the owning frame.
- GpuTextureSlot replaces bindless handles. Unassigned is a loud
uint.MaxValue sentinel rather than a silent resolve to slot 0 — the
failure mode behind the magenta 1x1 UI placeholder bug. Renderers
needing a fallback take the device's really-registered default slot.
- Renderers always speak GL winding/viewport conventions; the Vulkan
backend compensates with a negative viewport height in exactly one
mapping function.
Verified while writing the plan: acdream's cameras already build
[0,1]-NDC projections (PortalProjection.cs:12-13), which is Vulkan's
convention. No projection rework is needed and depth precision improves,
at the cost of shifted z-fight patterns — the one pre-approved divergence
class, registered per instance at V7.
Gate: Release build green; App suite 3,785 passed / 3 skipped (3,763
baseline plus 22 new contract tests). Note for later slices, recorded in
the plan: run the suite in Release. LandblockBuildOriginTests'
far-strip test asserts behavior that LandblockStreamer.cs:505 deliberately
turns into a loud Debug.Assert in Debug builds, so a Debug run shows one
pre-existing failure that is not a regression.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1610 lines
91 KiB
Markdown
1610 lines
91 KiB
Markdown
# acdream — project instructions for Claude
|
||
|
||
## Goal
|
||
|
||
Build **acdream**, a modern open-source C# .NET 10 Asheron's Call client.
|
||
A faithful port of the retail AC client's behavior to modern C# + Silk.NET,
|
||
with a plugin API the original never had.
|
||
|
||
**The code is modern. The behavior is retail.**
|
||
|
||
Every AC-specific algorithm is ported from the **named retail decomp**
|
||
at `docs/research/named-retail/` — Sept 2013 EoR build PDB (18,366
|
||
named functions, 5,371 named struct/class types) + Binary Ninja
|
||
pseudo-C with 99.6% function-name recovery + verbatim retail header
|
||
struct definitions. The older Ghidra `FUN_xxx` chunks under
|
||
`docs/research/decompiled/` (22,225 functions, 688K lines) remain a
|
||
fallback for chunk-by-chunk address-range navigation. **Grep
|
||
`named-retail/acclient_2013_pseudo_c.txt` by `class::method` BEFORE
|
||
decompiling fresh.** The code around those algorithms is modern C#
|
||
with clean architecture. The plugin API exposes game state through
|
||
well-defined interfaces.
|
||
|
||
**Architecture:** `docs/architecture/acdream-architecture.md` is the
|
||
single source of truth for how the client is structured. All work must
|
||
align with this document. When the architecture doc and reality diverge,
|
||
update one or the other — never leave them out of sync.
|
||
|
||
**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 LANDED 2026-07-21. M4 — Live in the world ACTIVE.** Retail
|
||
casting, spellbook/component-book UI, active effects, recall/portal-space
|
||
presentation, the R6 locomotion/collision/projectile/teleport/radar rebaseline,
|
||
and the final two-client portal-out/materialization observer flow are
|
||
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
||
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
||
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
||
Release builds; the current performance-program checkpoint passes
|
||
3,763 App tests / 3 skips and 8,826 complete-solution tests / 5 skips.
|
||
|
||
**M4 feature work order (active after the Modern Runtime closeout and Slice L
|
||
deferral):** the six-slice pre-M4 world-interaction completion
|
||
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
||
spell-bar overflow, status Use/Assess, assessment information,
|
||
equipped-child picking, vendor browsing, and authoritative vendor
|
||
transactions. Slices 1–3 plus the adjacent shared-cooldown presentation are
|
||
user-accepted, including exact response flags, independent examination
|
||
window, inscription transaction, complete creature/item/spell reports,
|
||
favorite-spell press/right-click behavior, modern scarab/prismatic formula,
|
||
DAT component icons, foreground stacking, and authored 310 x 400 extent.
|
||
Resume at Slice 4 equipped-child world picking, then vendor browse and
|
||
authoritative transactions.
|
||
|
||
**Modern Runtime/performance status:** Slices A–K of
|
||
`docs/plans/2026-07-24-modern-runtime-architecture.md` are complete. Slice L is
|
||
parked at its L1 implementation checkpoint by user direction on 2026-07-27.
|
||
Production world-mesh streaming now consumes the validated machine-local
|
||
`acdream.pak` through `IPreparedAssetSource`; live DAT extraction remains
|
||
explicit bake/equivalence/UI-Studio tooling. Capped, uncapped, dense-Arwic,
|
||
lifetime, full-suite, and user-visual gates passed. Process allocation fell
|
||
41.8–61.9%, GC pause time fell 48.4–54.1%, uncapped CPU/GPU p99 improved
|
||
17.0%/20.6%, and invalid Setup-probe exceptions fell to zero. Slice D unified
|
||
typed residency policy/accounting and bounded retained caches. Slice E now
|
||
enforce typed completion admission, generation-scoped old-world quiescence,
|
||
metered full-window/recenter retirement, and retained cursor-budgeted
|
||
render/physics/static/building/EnvCell publication. The canonical reveal
|
||
generation now owns one exact destination reservation across every typed
|
||
budget dimension; incomplete portal destinations stay in the authored tunnel
|
||
and receive retail's centered wait cue instead of forced early reveal. E6
|
||
deterministic/resource suites, Release build, complete tests, capped/uncapped
|
||
nine-stop routes, and pinned dense Arwic pass on exact binary `91e82c3c`.
|
||
Every checkpoint converged without pending publication, retirement, upload,
|
||
warmup, or class backlog. The user explicitly approved Slices F–L, including
|
||
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. G0–G5 are
|
||
complete. G4's 46,599-comparison automated gate has zero mismatches, the user
|
||
accepted the physical-display visual matrix, and G5 removed the production
|
||
`InteriorEntityPartition`. The ordinary production profile sustains 519.7 FPS
|
||
with CPU/GPU p50 1.869/1.096 ms and 652.1/928.3 MiB working/private memory.
|
||
The G5 profile's 22.34 KiB/frame remainder led into Slice I1. That slice is
|
||
now complete: player, remote, projectile, camera, and grounded
|
||
walkable-publication profiles each measure 0 B/resolve, with bit-identical
|
||
fresh-vs-reused results and complete reset/reentrancy gates. Slice I2 is also
|
||
complete: immutable physics/containment BSP, polygon, Setup, bounds, and
|
||
EnvCell-topology records preserve source float bits and ordering across
|
||
synthetic corruption gates and representative installed DATs without changing
|
||
production traversal. Slice I3 is complete: bake-tool 4 appends typed GfxObj,
|
||
Setup, CellStruct, and EnvCell-topology collision payloads; the collision
|
||
interface shares the render package's single mmap. Two complete
|
||
29,908,271,024-byte bakes produced 2,232,170 typed keys, zero failures, and the
|
||
same SHA-256 across 16 and 7 workers. Corruption, cancellation, installed
|
||
package, Release build, and 8,371-test / 5-skip gates pass.
|
||
Slice I4 is complete: every current containment, overlap, walkable, and
|
||
six-path moving-collision entry point has an integer-indexed flat shadow port.
|
||
The exact referee passes 13 focused tests, 7,500 installed-DAT comparisons,
|
||
1,200 complete resolver frames, and 10,000 warmed iterations at zero managed
|
||
bytes with no mismatch. Release build and 8,384 solution tests / 5 skips pass.
|
||
Slice I5 is complete: immutable near-build collision closures and strict live
|
||
collision publication install graph and prepared-flat views under canonical
|
||
ownership. Cancellation, demotion, rehydration, revisit, removal, and reset
|
||
gates pass. The lifecycle/reconnect and nine-stop routes sampled 14,064 exact
|
||
queries with zero mismatch, fault, or artifact; graph/flat cell residency
|
||
matched at every stable checkpoint and teardown converged. Release build and
|
||
8,396 solution tests / 5 skips pass.
|
||
Slice I is complete. I6 made gameplay collision flat-authoritative at
|
||
`068a0651`; its strict dense-Arwic gate matched 46,309/46,309 queries and the
|
||
physical nine-stop soak converged. After user collision acceptance, I7 removed
|
||
every production parsed collision graph at `82f8d4f8`. Release build and 8,413
|
||
tests / 5 skips pass; both exact-binary connected routes report `0/0/0` parsed
|
||
graph residency at every stable checkpoint with graceful teardown. The
|
||
cutover rollback is
|
||
`git revert 068a06518dd710ca5e7f166754cbb7789ca1ed0f`; graph-removal rollback
|
||
is `git revert 82f8d4f82e24ad85042ac3e25c4f6464bebce758`.
|
||
Slice J0 is complete at `b632672e`: `AcDream.Runtime` now provides the enforced
|
||
presentation-independent assembly boundary without moving gameplay behavior.
|
||
Four dependency guards pass. J1 is complete at `854d9e9c`: borrowed runtime
|
||
views, typed synchronous commands, ordered deltas, the instance clock,
|
||
generation/lifecycle contracts, and teardown acknowledgements project the same
|
||
canonical App owners without a mirror or extra frame. Runtime tests pass 13/13,
|
||
App tests pass 3,838/3 skips, the complete Release suite passes 8,424/5 skips,
|
||
and the exact-binary seven-checkpoint connected lifecycle/reconnect gate passes
|
||
with graceful exits. J2 is complete at `75930787`: the canonical
|
||
`WorldSession` generation, connect/enter/tick/replace transaction, ordered
|
||
inbound route, and retryable teardown acknowledgement now live in Runtime.
|
||
App retains only option conversion, graphical callbacks, and one borrowed
|
||
inertable UI command projection. Runtime tests pass 79/79, App tests pass
|
||
3,776/3 skips, the complete Release suite passes 8,428/5 skips, and the exact-
|
||
binary seven-checkpoint gate passes with graceful exits and zero render-shadow
|
||
mismatches or pending deltas. J3.1 moved accepted physics timestamp/snapshot
|
||
and parent-relation owners into Runtime at `f7442d13`. J3.2 is complete at
|
||
`f46ddb5c`: `RuntimeEntityDirectory` now owns the only active GUID/incarnation,
|
||
teardown tombstone, local-ID/reverse, accepted-wire, parent, and session-
|
||
operation maps; `RuntimeEntityRecord` is presentation-free and App records are
|
||
graphical sidecars. Runtime tests pass 110/110, App tests pass 3,758/3 skips,
|
||
the complete Release suite passes 8,441/5 skips, and the exact-binary seven-
|
||
checkpoint lifecycle/reconnect gate passes with graceful exits, zero render-
|
||
shadow mismatches, and zero pending deltas. J3.3 is complete at `420e5eea`
|
||
plus exact-spatial correction `e937cc36`: `LiveEntityProjectionStore` and every
|
||
materialized presentation/spatial owner now use exact `RuntimeEntityKey`; the
|
||
temporary GUID-shaped compatibility view is deleted. App tests pass 3,761/3
|
||
skips, the complete Release suite passes 8,444/5 skips, and the final exact-
|
||
binary seven-checkpoint gate passes with two graceful exits, zero failures,
|
||
zero render-shadow mismatches, and zero pending deltas. J3.4 is complete at
|
||
`5ef8b537`: `RuntimeEntityObjectLifetime` owns the sole entity directory and
|
||
live `ClientObjectTable`; App, routing, interaction, and UI borrow the exact
|
||
instances. Runtime tests pass 118/118, App tests pass 3,762/3 skips, the
|
||
complete Release suite passes 8,453/5 skips, and the exact-binary
|
||
seven-checkpoint gate passes with graceful exits, zero failures, zero
|
||
render-shadow mismatches, and zero pending deltas. J3.5 is complete at
|
||
`ce3ac310`: Runtime issues identity before App hydration, publishes entity and
|
||
inventory commits through one per-generation sequence, and supplies the same
|
||
direct borrowed views to graphical and no-window hosts. Runtime tests pass
|
||
134/134, App tests pass 3,765/3 skips, the complete Release suite passes
|
||
8,472/5 skips, and the exact-binary seven-checkpoint gate passes with graceful
|
||
exits, zero failures, zero render-shadow mismatches, and zero pending deltas.
|
||
J3.6 is complete at `119b7c11`: exact teardown receipts precede fallible
|
||
callbacks, re-entrant commits retain one monotonic synchronous stream,
|
||
accepted-channel versions reject displaced callbacks, and reset/direct
|
||
disposal converge the complete ownership ledger to zero. Runtime tests pass
|
||
146/146, App tests pass 3,765/3 skips, the complete Release suite passes
|
||
8,484/5 skips, and both the exact lifecycle/reconnect and canonical nine-stop
|
||
routes pass with graceful zero-code exits and no failures. J3 is closed; J4
|
||
gameplay-state lifetime groups are active. J4.1 is complete at `c9d25ade`:
|
||
`RuntimeCommunicationState` owns the exact chat transcript, reply/retell
|
||
targets, negotiated rooms, friends, and squelch state; graphical and no-window
|
||
consumers borrow them. Runtime tests pass 152/152, App tests pass 3,765/3
|
||
skips, the complete Release suite passes 8,494/5 skips, and the exact-binary
|
||
seven-checkpoint lifecycle/reconnect gate passes with graceful exits and zero
|
||
failures. J4.2 is complete at `011efbea`: `RuntimeInventoryState` owns
|
||
external-container, item-mana, shortcut/component, shared-busy,
|
||
use-reservation, and one-request-at-a-time state while borrowing J3's exact
|
||
object table. Runtime tests pass 157/157, App tests pass 3,770/3 skips, the
|
||
complete Release suite passes 8,515/5 skips, and the exact-binary
|
||
seven-checkpoint lifecycle/reconnect gate passes with graceful exits and zero
|
||
failures. J4.3 is complete at `d02a12ce`: `RuntimeCharacterState` owns the
|
||
exact coupled spellbook/local-player graph; content, routing, retained UI,
|
||
reset, and shutdown borrow it, and the duplicate desired-component snapshot is
|
||
deleted. Runtime tests pass 162/162, App tests pass 3,772/3 skips, the complete
|
||
Release suite passes 8,522/5 skips, and the exact-binary seven-checkpoint
|
||
lifecycle/reconnect gate passes with graceful exits and zero failures. J4.4 is
|
||
complete at `dcb61efb`: Runtime owns character options and server run/jump
|
||
projections, exposes exact borrowed J4 gameplay views, and routes retained UI
|
||
plus future no-window state commands through one synchronous generation-gated
|
||
seam. Runtime tests pass 169/169, App tests pass 3,777/3 skips, the complete
|
||
Release suite passes 8,534/5 skips, and the exact-binary seven-checkpoint gate
|
||
passes with graceful exits and zero failures. J4.5 is complete at `89e6b207`:
|
||
Runtime owns the one shortcut manager, graphical/no-window command effects use
|
||
the exact inventory/character owners, item-use UI cannot create a second
|
||
transaction gate, and the combined failure-safe ownership ledger converges.
|
||
Runtime tests pass 175/175, App tests pass 3,780/3 skips, the complete Release
|
||
suite passes 8,544/5 skips, and both exact-binary lifecycle/reconnect and
|
||
canonical nine-stop gates pass with graceful exits and zero failures. J5.1 is
|
||
complete at `b298f99f`: `RuntimeActionState` owns the exact selection, combat,
|
||
and temporary target-mode graph; Program, plugins, retained UI, session
|
||
routing, typed commands/views, and shutdown borrow its children, while the App
|
||
interaction-state class and private controller fallback are deleted. Runtime
|
||
tests pass 182/182, App tests pass 3,779/3 skips, the complete Release suite
|
||
passes 8,550/5 skips, and the exact-binary seven-checkpoint
|
||
lifecycle/reconnect gate passes with graceful exits and zero failures. J5.2 is
|
||
complete at `f5f7b417`: `RuntimeActionState.Transactions` owns the strict use
|
||
gate, exact use/appraisal identity, typed interaction FIFO, and exact
|
||
post-arrival pickup token while borrowing J4's sole inventory busy/request
|
||
gate. App retains picking, movement/transport, and presentation only. Runtime
|
||
tests pass 197/197, App tests pass 3,775/3 skips, the complete Release suite
|
||
passes 8,561/5 skips, and the exact-binary seven-checkpoint gate passes with
|
||
graceful exits and zero failures. J5.3 is complete at `20df9d15`:
|
||
`RuntimeActionState` owns the exact combat-attack, combat-target, combat-mode,
|
||
and spell-cast intent children. Retained bars and graphical input borrow those
|
||
owners; direct Runtime commands drive the same operations and snapshots.
|
||
Runtime tests pass 223/223, App tests pass 3,754/3 skips, the complete Release
|
||
suite passes 8,566/5 skips, and the exact-binary seven-checkpoint gate passes
|
||
with graceful exits and zero failures. J5.4 is complete at `aa3f4a60`:
|
||
`RuntimeLocalPlayerMovementState` owns the exact local movement controller,
|
||
construction seam, autorun latch, typed view, and terminal ledger; graphical
|
||
input and direct Runtime commands borrow the same state, and outbound
|
||
MTS/jump/AP cadence lives in Runtime. Runtime tests pass 303/303, App tests
|
||
pass 3,716/3 skips, the complete Release suite passes 8,575/5 skips, and both
|
||
the exact-binary lifecycle/reconnect and canonical nine-stop movement gates
|
||
pass with graceful exits and zero failures. J5.5 is complete at `7e6033d0`:
|
||
`RuntimePhysicsState` owns the sole per-session engine, production cache/cell
|
||
graph, transition scratch, shadow registry, typed collision admissions,
|
||
canonical body/host/remote components, ordinary/remote worksets, simulation,
|
||
and full-cell commits. App publishes prepared collision and projects committed
|
||
snapshots only. Runtime tests pass 314/314, App tests pass 3,718/3 skips, the
|
||
complete Release suite passes 8,588/5 skips, and both exact-binary
|
||
lifecycle/reconnect and canonical nine-stop collision/movement gates pass
|
||
with graceful exits and zero failures. J5.6 is complete at `2aee3356`:
|
||
Runtime owns the canonical projectile component/workset,
|
||
prediction/correction state, and unchanged retail projectile simulation while
|
||
App resolves immutable DAT shape data and projects committed frames. Runtime
|
||
tests pass 317/317, App tests pass 3,718/3 skips, 47 focused Core projectile
|
||
tests pass, the complete Release suite passes 8,591/5 skips, and the exact
|
||
arrow/bolt/spell, lifecycle/reconnect, and canonical nine-stop gates pass with
|
||
graceful exits and zero failures. J5.7 and J5 are complete at `cdee7a4b`:
|
||
Runtime owns accepted remote-body/vector activation, final simulation
|
||
retirement, terminal physics/shadow/workset cleanup, and one combined
|
||
entity/object/gameplay/physics ownership ledger. The Runtime-only fixture
|
||
drives movement, use, combat, casting, and projectiles without loading
|
||
presentation/backend assemblies. Runtime tests pass 323/323, App tests pass
|
||
3,717/3 skips, the complete Release suite passes 8,596/5 skips, and the exact
|
||
lifecycle/reconnect plus canonical nine-stop gates match binary/source,
|
||
converge every backlog, and exit gracefully with zero failures. J6
|
||
world/portal/environment and projection-handshake ownership is active. J6.1
|
||
landed at `902076c0`: Runtime owns the instance-scoped Dereth calendar,
|
||
server-synchronized clock, weather, day-group selection, and AdminEnvirons
|
||
state while App only projects immutable DAT sky definitions into rendering.
|
||
Its 332 Runtime tests, 3,718 App tests / 3 skips, 8,611 complete Release tests
|
||
/ 5 skips, and exact-binary lifecycle/reconnect route pass. J6.2 landed at
|
||
`a6860d55` plus `acb845d8`: Runtime owns the sole reveal generation,
|
||
destination/readiness latch, materialization/simulation edge, viewport,
|
||
completion, cancellation, wait cue, and portal count. App's lifecycle mirror
|
||
is deleted. Its 349 Runtime tests, 3,716 App tests / 3 skips, 8,626 complete
|
||
Release tests / 5 skips, and exact-binary lifecycle/reconnect route pass with
|
||
zero reveal invariants. J6.3 landed at `6a063a27`: Runtime owns wrap-safe F751
|
||
history, both accepted F751/Position orders, the exact destination, and
|
||
generation/sequence/cell placement and materialization validation. App's
|
||
transit coordinator and destination mirror are deleted. Its 360 Runtime tests,
|
||
3,710 App tests / 3 skips, 8,631 complete Release tests / 5 skips, and
|
||
exact-binary lifecycle/reconnect route pass. J6.4 landed at `18d17d8b`:
|
||
Runtime owns the exact generation/cell-scoped host acknowledgement suffix and
|
||
App owns only retryable graphical resource receipts. Its 365 Runtime tests,
|
||
3,716 App tests / 3 skips, 8,642 complete Release tests / 5 skips, and
|
||
exact-binary lifecycle/reconnect route pass with every environment,
|
||
transit, and pending-host ledger at zero. J6 is complete. J7.1 created the
|
||
single `GameRuntime` root at `96ddd165`; J7.2/J7.3 cut graphical production
|
||
and shutdown over at `ce41efb9`. Its 383 Runtime tests, 3,717 App tests /
|
||
3 skips, 8,661 complete Release tests / 5 skips, lifecycle/reconnect route,
|
||
canonical nine-stop route, and the user's 2026-07-27 exact post-cutover visual
|
||
matrix pass. J7 is closed. J8 closed Slice J at `a9a822f2`: graphical and
|
||
no-window hosts share one `GameRuntime`, one retryable canonical-generation
|
||
reset transaction, and one gameplay-owner graph. Its 395 Runtime tests, 3,731
|
||
App tests / 3 skips, 8,696 complete Release tests / 5 skips, exact-binary
|
||
lifecycle/reconnect route, and canonical nine-stop route pass. Slice K
|
||
Linux headless/multi-session implementation is complete. K0 completed at
|
||
`aada8a37`: `AcDream.Headless` references only Runtime, strict no-connect
|
||
validation and dependency/loaded-assembly guards pass, and the local Ubuntu
|
||
closure passes 600 Core.Net, 119 Content, 395 Runtime, and 12 Headless tests.
|
||
K1 completed at `f8cb840f`: portable XDG/Windows paths, strict config,
|
||
redacted credentials, direct Runtime command/entity/portal routes, and the
|
||
production single-session host pass the same real ACE connect, portal,
|
||
graceful-reconnect, and terminal-teardown gate on Windows and Linux/WSL.
|
||
Runtime tests pass 402/402, Headless tests pass 25/25, the complete Windows
|
||
Release suite passes 8,728/5 skips. K2 completed at `7e8acb74` plus
|
||
`38e83640`: the monotonic absolute-deadline scheduler, shared Runtime
|
||
local-player frame sequence, complete generation-gated bot commands, and
|
||
ordered lifecycle/entity/inventory/chat/movement/portal/combat/command event
|
||
stream pass 411 Runtime tests, 31 Headless tests, 3,732 App tests / 3 skips,
|
||
and 8,744 complete Release tests / 5 skips. K3 completed across `12b500d3`,
|
||
`9569dadb`, `b6547ff3`, and `3f340125`: exact process content leases,
|
||
immutable magic/terrain/bounded collision sharing, presentation-free
|
||
local-player/collision hydration, 1/5/10-session same-GUID/portal/reconnect
|
||
isolation, and policy-fault quarantine pass 412 Runtime, 42 Headless, 3,722 App
|
||
tests / 3 skips, and 8,758 complete Release tests / 5 skips. The connected
|
||
closeout adds direct single-session CLI launch, four-stop portal routing,
|
||
external teleport handling, retail-correct normal Position versus
|
||
ForcePosition projection, explicit-only collision diagnostics, visible Windows
|
||
and native Linux production runs, user-accepted smooth retail-observer
|
||
movement, and ACE-confirmed graceful logout. Final gates pass 412 Runtime, 47
|
||
Headless, 3,722 App / 3 skips, and 8,764 complete Release tests / 5 skips.
|
||
K4 implementation is complete through `776482da`. `cb512fd0` added one
|
||
process-scheduler observation deadline and corrected pre-connect deadline
|
||
arming plus a Linux sub-millisecond `Task.Delay` busy loop. `97c174fb` added
|
||
1/5/10/30-root content, same-GUID, portal, reconnect, and two-hour simulated
|
||
endurance gates; `bd236ce5` added death and randomized cancellation coverage;
|
||
`776482da` committed the diagnostic-only 30-session resource envelope and
|
||
exact running/terminal ownership checks. Windows and native Ubuntu Headless
|
||
tests pass 67/67; the complete Release suite passes 8,784 / 5 skips. The exact
|
||
`776482da` native Linux two-account process ran for ten minutes with 20
|
||
consecutive passing periodic samples, zero faults/reconnect debt, bounded
|
||
scheduler cadence, and every resource dimension inside its ceiling. SIGINT
|
||
produced ACE-confirmed graceful logout for both accounts and a passing final
|
||
`disposed` sample: two converged Runtime roots, zero entities/inventory/host
|
||
or content leases, and disposed shared content. K4 and Slice K are closed;
|
||
Slice L Linux graphical/platform work is parked at its L1 implementation
|
||
checkpoint by user direction on 2026-07-27. L0 completed at `66f114b2`:
|
||
`ApplicationPathSet` is the shared XDG/Windows path authority;
|
||
`GraphicalHostPlatformServices` owns one OS/architecture/RID, native manifest,
|
||
and frame-waiter factory; Linux uses an absolute `CLOCK_MONOTONIC` wait;
|
||
settings, keybindings, Studio, diagnostics, and user plugins consume portable
|
||
paths with no-overwrite Windows migration; and the RID-safe App publish plus
|
||
Ubuntu graphical CI/package gate pass. Windows passes 415 Runtime, 67
|
||
Headless, 543 UI, and 3,736 App tests / 3 skips; the complete Release suite
|
||
passes 8,799 / 5 skips. Native Ubuntu passes the portable closure, 14 L0 App
|
||
tests, and exact `linux-x64` publish. L1 context/extension/native-backend
|
||
validation implementation landed at `11501d52`: immutable
|
||
Win32/X11/Wayland selection, packaged GLFW 3.4 preference, atomic capability
|
||
reporting, and active bindless/draw-parameters/MDI/SSBO/timer/FBO/persistent-
|
||
buffer probes now gate renderer construction. Native Windows AMD passes every
|
||
active probe and terminal graphical/audio ownership converges to zero. WSLg
|
||
X11/Wayland correctly reject Mesa D3D12/llvmpipe because mandatory
|
||
`GL_ARB_bindless_texture` is absent. App tests pass 3,763 / 3 skips and the
|
||
complete Release suite passes 8,826 / 5 skips. A supported physical Linux
|
||
AMD/NVIDIA driver row remains the first gate when work resumes; that gate and
|
||
L2–L6 are deferred.
|
||
J0's rollback is
|
||
`git revert b632672e5ccabfb44c551e08f1c411ab2669c44a`.
|
||
J1's rollback is
|
||
`git revert 854d9e9cd13092bd5aaa3cf025d73eeb4600e9f8`.
|
||
J2's rollback is
|
||
`git revert 75930787741db40a83eab8663e4464dce8d687ba`.
|
||
J3.2's rollback is
|
||
`git revert f46ddb5cdb1e145752bea49aeb1d62bfe71284d3`.
|
||
J3.3's rollback is, newest first,
|
||
`git revert e937cc36df39cf6ea1eaba2f9c0243d1929da702` then
|
||
`git revert 420e5eea70fd2c29cf9c8614e6298a1f84d64045`.
|
||
J3.4's rollback is
|
||
`git revert 5ef8b5371d8990f0380acd939e71cd711289d429`.
|
||
J3.5's rollback is
|
||
`git revert ce3ac310d92722ffb637e81cb1957458874dd220`.
|
||
J3.6's rollback is
|
||
`git revert 119b7c115107245546180b2f5cb3cb44c7d5476a`.
|
||
J4.1's rollback is
|
||
`git revert c9d25ade50c0a5c4f7db5b6cca680e01e35cc18e`.
|
||
J4.2's rollback is
|
||
`git revert 011efbeaa72509b35ea7f4a442e50a2377ae1ea4`.
|
||
J4.3's rollback is
|
||
`git revert d02a12ceac54d035797b4c58f2fc2f0ccad9a4d6`.
|
||
J4.4's rollback is
|
||
`git revert dcb61efb5af6c12bd619369e28cf11bacc37bc73`.
|
||
J4.5's rollback is
|
||
`git revert 89e6b207f81946b40c38c8dc6597817ad99fc6e0`.
|
||
J5.1's rollback is
|
||
`git revert b298f99f913249d299796e6a71f7a9b6b283ac1a`.
|
||
J5.2's rollback is
|
||
`git revert f5f7b4177f449ebcad9724bfea263ef73ab60255`.
|
||
J5.3's rollback is
|
||
`git revert 20df9d155db50706a42420d60a9b15861cf4bfe7`.
|
||
J5.4's rollback is
|
||
`git revert aa3f4a60f87f2cbebc3d8ecd5e33d779b7fb13c1`.
|
||
J5.5's rollback is
|
||
`git revert 7e6033d0adcd0b572c20e89dd275746fb442d52f`.
|
||
J5.6's rollback is
|
||
`git revert 2aee33569f0268d7bc0f4f52437dfa0b405432a4`.
|
||
J5.7's rollback is
|
||
`git revert cdee7a4b49addb5e1500753f6a885f7c899bd0f0`.
|
||
J6.1's rollback is
|
||
`git revert 902076c0a42079596a8a273e9be402776cabf4b0`.
|
||
J6.2's rollback is, newest first,
|
||
`git revert acb845d812574206da1693fb379fec8a6f175fcb` then
|
||
`git revert a6860d5563ae845a2cbb916abacf2b4aee515cdb`.
|
||
J6.3's rollback is
|
||
`git revert 6a063a27d4c805cd166d5a487afc290c34c400fd`.
|
||
J6.4's rollback is
|
||
`git revert 18d17d8bb17ed9ff3ed01da8273e2948abe6398b`.
|
||
J7.1's rollback is
|
||
`git revert 96ddd16539e670a5ce9b1f86677530e3fbc3f635`.
|
||
J7.2/J7.3's rollback is
|
||
`git revert ce41efb9e5938f79a7580476b949d172f52a1e69`.
|
||
J8's rollback is
|
||
`git revert a9a822f206cc6021bc099988144488f3bd7c397b`.
|
||
K0's rollback is
|
||
`git revert aada8a37c1e933f9f0a3f41dc4f05615b01023bc`.
|
||
K1's rollback is
|
||
`git revert f8cb840fb15b27f61afa5c33cad6c4dba8584949`.
|
||
K2's rollback is, newest first,
|
||
`git revert 38e83640d907ee3e0819f3b38ca117ca60914d6b` then
|
||
`git revert 7e8acb74dd20931d5ba88430fd100cd074a32e4d`.
|
||
K3's connected-closeout rollback is
|
||
`git revert 3f3401257c83bdd9f557eb577ba08cf2e5ec9a06`; its earlier automated
|
||
checkpoint rollbacks remain recorded in the Slice K plan.
|
||
K4's current rollbacks, newest first, are
|
||
`git revert 776482da8225d88a7c6128022d137d26f9266a87`,
|
||
`git revert bd236ce553718cfb1248703f4a2b17b35733b11c`,
|
||
`git revert 97c174fbb2d72fb94e2fe07448bb257989ed8941`, then
|
||
`git revert cb512fd0916fbf49285c9b64df1630c5a69a0d20`.
|
||
L0's rollback is
|
||
`git revert 66f114b258c0c37b51a7c0e04ca0d6441f4b1699`.
|
||
L1's implementation-checkpoint rollback is
|
||
`git revert 11501d52cad942850d88f83490710faf1d5cdf28`.
|
||
Slice H's retained UI/frame, bounded-light, and pooled/borrowed/direct network
|
||
work is complete; the exact seven-checkpoint connected lifecycle/reconnect gate
|
||
passes. Slice I is closed in
|
||
`docs/plans/2026-07-25-modern-runtime-slice-i.md`; the coherent Slice J campaign
|
||
executes from `docs/plans/2026-07-25-modern-runtime-slice-j.md`; J3.6 closeout
|
||
is `docs/research/2026-07-26-slice-j3-6-lifetime-closeout.md`; J4 executes from
|
||
`docs/plans/2026-07-26-modern-runtime-slice-j4.md`; J4.1 closeout is
|
||
`docs/research/2026-07-26-slice-j4-1-communication-state.md`; J4.2 closeout is
|
||
`docs/research/2026-07-26-slice-j4-2-inventory-state.md`; J4.3 closeout is
|
||
`docs/research/2026-07-26-slice-j4-3-character-state.md`; J4.4 closeout is
|
||
`docs/research/2026-07-26-slice-j4-4-character-projections.md`; J4.5 closeout
|
||
is `docs/research/2026-07-26-slice-j4-5-gameplay-state-closeout.md`; J5.1
|
||
closeout is
|
||
`docs/research/2026-07-26-slice-j5-1-canonical-action-state.md`; J5.2 closeout
|
||
is `docs/research/2026-07-26-slice-j5-2-interaction-transactions.md`; J5.3
|
||
closeout is `docs/research/2026-07-26-slice-j5-3-combat-magic-intent.md`; J5.4
|
||
closeout is
|
||
`docs/research/2026-07-26-slice-j5-4-local-movement-ownership.md`; J5.5
|
||
closeout is
|
||
`docs/research/2026-07-26-slice-j5-5-physics-remote-ownership.md`; J5.6
|
||
closeout is
|
||
`docs/research/2026-07-26-slice-j5-6-projectile-ownership.md`; J5.7 closeout is
|
||
`docs/research/2026-07-26-slice-j5-7-simulation-ownership-closeout.md`; J6.3
|
||
closeout is
|
||
`docs/research/2026-07-26-slice-j6-3-teleport-correlation.md`; J6 closeout is
|
||
`docs/research/2026-07-26-slice-j6-4-host-acknowledgement.md`. J7 closeout is
|
||
`docs/research/2026-07-26-slice-j7-game-runtime-root-cutover.md`; J8/Slice-J
|
||
closeout is
|
||
`docs/research/2026-07-27-slice-j8-no-window-closeout.md`; K0 closeout is
|
||
`docs/research/2026-07-27-slice-k0-portability-boundary.md`; K1 closeout is
|
||
`docs/research/2026-07-27-slice-k1-single-session-headless.md`; K2 closeout is
|
||
`docs/research/2026-07-27-slice-k2-deterministic-bot-parity.md`; Slice K is
|
||
closed in `docs/plans/2026-07-26-modern-runtime-slice-k.md`; K4 evidence is
|
||
`docs/research/2026-07-27-slice-k4-resource-telemetry-checkpoint.md`; Slice L
|
||
executes from `docs/plans/2026-07-26-modern-runtime-slice-l.md`; L0 evidence is
|
||
`docs/research/2026-07-27-slice-l0-platform-services.md`; L1 checkpoint
|
||
evidence is
|
||
`docs/research/2026-07-27-slice-l1-graphical-capability-checkpoint.md`. The exact G4 visual
|
||
rollback
|
||
is `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`; do not revert G3 or
|
||
the portal-warmup corrections. Evidence:
|
||
`docs/research/2026-07-25-slice-h-closeout.md` and
|
||
`docs/research/2026-07-25-slice-g5-production-profile.md` and
|
||
`docs/research/2026-07-25-slice-i1-transition-scratch.md` and
|
||
`docs/research/2026-07-25-slice-i2-flat-collision-schema.md` and
|
||
`docs/research/2026-07-25-slice-i3-prepared-collision-package.md` and
|
||
`docs/research/2026-07-25-slice-i4-flat-bsp-differential.md` and
|
||
`docs/research/2026-07-25-slice-i5-dual-collision-shadow.md` and
|
||
`docs/research/2026-07-25-slice-i6-flat-production-cutover.md` and
|
||
`docs/research/2026-07-25-slice-i7-closeout.md` and
|
||
`docs/research/2026-07-25-slice-j1-runtime-contract-closeout.md` and
|
||
`docs/research/2026-07-25-slice-j2-session-lifetime-closeout.md` and
|
||
`docs/research/2026-07-25-slice-j3-2-canonical-entity-directory-closeout.md` and
|
||
`docs/research/2026-07-25-slice-j3-3-exact-projection-store.md` and
|
||
`docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md` and
|
||
`docs/research/2026-07-26-slice-j3-5-canonical-delta-stream.md` and
|
||
`docs/research/2026-07-26-slice-j3-6-lifetime-closeout.md`.
|
||
|
||
**Structural prerequisite before new M4 subsystem work:** all eight
|
||
behavior-preserving `GameWindow` decomposition slices and the automated
|
||
closeout landed 2026-07-22. The class is now a 1,622-line native
|
||
composition/callback shell, down 14,101 lines (89.7%) from baseline. Ordered
|
||
startup, update/render frame graphs, native/input callbacks, settings,
|
||
session/reset wiring, canonical checkpoint capture, and retryable shutdown are
|
||
owned by focused typed collaborators without a service locator or stored
|
||
window back-reference. All 293 focused tests, App Release 3,462/3, the complete
|
||
Release suite 7,835/5, connected lifecycle/reconnect, two fresh-process
|
||
canonical nine-stop soaks, three corrected-diff reviews, and the exact Slice-7
|
||
framebuffer comparison pass. Issue #232 is closed without loosening process
|
||
limits. The user's connected visual matrix passed 2026-07-23, including the
|
||
post-closeout radar correction; the structural campaign is complete and M4
|
||
feature bodies may resume after any active regression gate.
|
||
The capped/RDP jump-presentation cadence alias is deferred as issue #235:
|
||
uncapped Release presentation is smooth, while physics, collision, and wire
|
||
truth remain correct.
|
||
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
|
||
`docs/architecture/code-structure.md`. **Carried:** #153, #116, remaining
|
||
R6 ownership cleanup, TS-50/TS-51/TS-53, Modern Runtime Slice L, and #225's
|
||
lifestone/particle alpha visual gate.
|
||
|
||
Start structural work at `memory/project_gamewindow_decomposition.md` and
|
||
`docs/architecture/code-structure.md`; start
|
||
magic follow-up at `claude-memory/project_magic_ui_and_casting.md`; start
|
||
render/streaming work at `claude-memory/project_render_pipeline_digest.md`.
|
||
|
||
Documentation entry point: [`docs/README.md`](docs/README.md).
|
||
|
||
For canonical state, read in this order:
|
||
- [`docs/plans/2026-07-27-vulkan-campaign.md`](docs/plans/2026-07-27-vulkan-campaign.md) — **ACTIVE: Campaign V, OpenGL → Vulkan.** The pinned RHI contract, the Vulkan technical decisions, the V0–V11 slice table with per-slice gates, and the subagent execution rules. Read this before touching anything under `src/AcDream.App/Rendering/`. ImGui dev tools and UI Studio are NOT ported and are deleted at V11.
|
||
- [`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 now the
|
||
roughly 1,600-line native composition/callback shell. 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 Claude'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 <exe>`
|
||
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 <output-path>
|
||
.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 <script>.cdb *>&1 |
|
||
Tee-Object -FilePath <log>
|
||
```
|
||
|
||
5. **User reproduces the scenario** in the retail client window
|
||
(jump on roof, hit wall, etc.). Breakpoints fire, log fills.
|
||
|
||
6. **cdb auto-detaches** when the threshold breakpoint fires `qd`.
|
||
Retail keeps running unaffected. Read the log offline.
|
||
|
||
### Known watchouts
|
||
|
||
- **PDB function names use snake_case for some classes**
|
||
(BSPTREE, CTransition, OBJECTINFO, COLLISIONINFO, SPHEREPATH) and
|
||
**PascalCase for others** (CPhysicsObj). The Binary Ninja decomp
|
||
shows snake_case for everything; the PDB has Turbine's actual
|
||
PascalCase for CPhysicsObj. Always look up symbols with `x` first
|
||
to find the actual name.
|
||
|
||
- **`bp acclient!Class::method`** sets a breakpoint by symbol. The
|
||
cdb command parser splits on `;`, so don't put `;` inside the
|
||
action string — use newlines or escape carefully.
|
||
|
||
- **Symbol path: do NOT use `.sympath srv*<server>;<local>`** — the
|
||
`;` is a cdb command separator, gets split. Use `.sympath <local>`
|
||
(no symbol server, just our refs/) since we don't need Microsoft
|
||
system DLL symbols.
|
||
|
||
- **Killing cdb kills the debuggee.** Use `qd` (quit detached) inside
|
||
a breakpoint action to detach cleanly. `Stop-Process cdb` will
|
||
take the retail client down with it.
|
||
|
||
- **High breakpoint hit rates produce game lag.** Each breakpoint hit
|
||
traps the process briefly. For frequent functions
|
||
(transitional_insert at ~10K/sec) the cumulative cost is enough to
|
||
make retail feel sluggish. Mitigate by setting a tight auto-detach
|
||
threshold (e.g., 30,000 hits) and/or moving counters to less-frequent
|
||
functions.
|
||
|
||
- **acclient.exe is 32-bit + uses thiscall.** When dumping struct
|
||
fields in breakpoint actions, `this` is in `ecx`. Use cdb's
|
||
`dt acclient!ClassName @ecx` for full struct dump.
|
||
|
||
### When NOT to use this
|
||
|
||
- **Pure code-port questions** — the decomp at `docs/research/named-retail/`
|
||
has the answer. Don't waste time on cdb if `grep` is enough.
|
||
- **Visual / rendering bugs** — debugger doesn't help with shaders or
|
||
framebuffers; use RenderDoc or similar.
|
||
- **Network protocol questions** — `holtburger` references + ACE source
|
||
+ Wireshark are the right tools, not cdb.
|
||
|
||
## MCP servers (live tooling)
|
||
|
||
Two MCP servers extend the static decomp + cdb workflow with live
|
||
introspection. **Ghidra MCP** requires Ghidra to be running with a
|
||
CodeBrowser open in the target project; **WireMCP** auto-loads at
|
||
Claude Code startup.
|
||
|
||
### Ghidra MCP (LaurieWired v1.4, HTTP)
|
||
|
||
Starts an HTTP server on **port 8080** (or **8081** if 8080 is
|
||
taken — first-open-wins) when a CodeBrowser tool opens a program.
|
||
Currently serving **`patchmem.gpr`** — the 2013 v11.4186 build with
|
||
full PDB applied, same source as `docs/research/named-retail/`. Use
|
||
this when grep'ing `acclient_2013_pseudo_c.txt` returns too much
|
||
noise and you want the decomp for one specific function or address
|
||
without dumping the whole file into context.
|
||
|
||
Probe: `curl http://127.0.0.1:8081/methods?limit=3`
|
||
|
||
Useful endpoints (GET unless noted):
|
||
|
||
- `/methods?limit=N` — function names
|
||
- `/list_functions?limit=N` — `Name at HHHHHHHH` lines
|
||
- `/decompile_function?address=0xHHHHHHHH` — decompiled C for one function
|
||
- `/function_xrefs?name=...` — callers / callees
|
||
- `/classes`, `/namespaces`, `/strings`
|
||
- POST `/rename_function_by_address`, POST `/set_decompiler_comment`
|
||
|
||
NO endpoints for: signature setting, namespace setting, script
|
||
execution, save-project. Those still require Ghidra's GUI or
|
||
`analyzeHeadless`. Full endpoint catalog + Ghidra project layout in
|
||
`memory/reference_ghidra_projects.md`.
|
||
|
||
### WireMCP (stdio, Node, user-scope)
|
||
|
||
Wraps `tshark` at `C:\Program Files\Wireshark\tshark.exe`
|
||
(auto-detected via the Windows fallback path in `WireMCP/index.js`).
|
||
Direct fit for ACE wire-protocol work — capture loopback
|
||
(`127.0.0.1:9000`) to cross-check inbound message parsing (`0xF61C`
|
||
movement, `0xF74A` pickup despawn, `0xF7DE` chat, etc.) against the
|
||
actual bytes, or diff ACE's outbound vs. the holtburger reference.
|
||
Replaces ad-hoc Wireshark sessions in the conversation.
|
||
|
||
Tools exposed:
|
||
|
||
- `capture_packets` — short live capture on an interface, returns JSON
|
||
- `get_summary_stats` — protocol hierarchy stats
|
||
- `get_conversations` — TCP/UDP conversation table
|
||
- `analyze_pcap` — parse a saved `.pcap` file
|
||
- `check_threats`, `check_ip_threats` — URLhaus / threat-feed lookups
|
||
- `extract_credentials` — grep for creds across protocols (rarely relevant)
|
||
|
||
Installed at `C:\Users\erikn\source\repos\WireMCP\` (clone of
|
||
`0xKoda/WireMCP`). Registered via `claude mcp add wiremcp --scope user`.
|
||
|
||
**When NOT to use WireMCP:** decoding the AC packet *format* — that
|
||
lives in `holtburger`, ACE, and `Chorizite.ACProtocol`. WireMCP shows
|
||
you the bytes on the wire; the reference repos tell you what they
|
||
mean.
|
||
|
||
## 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.
|
||
|
||
## Milestone discipline
|
||
|
||
acdream operates at **two altitudes** above the daily commit:
|
||
|
||
- **`docs/plans/2026-05-12-milestones.md`** — the morale + scope layer.
|
||
Seven milestones (M0–M7) from "Connect & explore" to "v1.0", each
|
||
defined by a concrete playable scenario and ~6–10 weeks of work. This
|
||
is where you orient when the project feels half-built and you're not
|
||
sure what to work on. Phases are too granular to feel like progress;
|
||
this doc is the multi-week target.
|
||
- **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap.
|
||
Phase-level index. This is where you orient when you know the
|
||
milestone and need the next concrete sub-phase.
|
||
|
||
**Work-order autonomy — the meta-rule.** You decide what to work on
|
||
next, always. **The user does NOT pick between phases, milestones, or
|
||
"what's next?" alternatives.** The milestone discipline + the
|
||
per-milestone phase list + the roadmap IS the work order — drive it.
|
||
Never ask the user "want me to start X or Y?" or present a menu of
|
||
options. If two next steps are genuinely equivalent, state which one
|
||
you picked and why in one sentence and start — don't ask. The user
|
||
retains the right to redirect if they think you're wrong, but the
|
||
default is **Claude drives, user reviews**. The user finds decision
|
||
fatigue from constant work-order choices draining — that's literally
|
||
what triggered the milestones doc on 2026-05-12. Honoring this rule is
|
||
the single biggest morale lever. This is the meta-rule that makes the
|
||
four below actually work.
|
||
|
||
**The four motivation-keeping rules:**
|
||
|
||
1. **One active milestone at a time.** Work that isn't on the critical
|
||
path to the current milestone gets filed in `docs/ISSUES.md` with
|
||
the appropriate `post-Mx` tag and muted. This is the single rule
|
||
that kills the "jumping between things" feeling. If a phase isn't
|
||
part of the current milestone, it doesn't get touched — even if
|
||
it's tempting, even if it would be "quick", even if it would be
|
||
"while I'm here."
|
||
|
||
2. **Frozen phases are off-limits.** Shipped-milestone phases are
|
||
frozen until M7's polish pass. Concretely: no rework on streaming,
|
||
chat, input, the WB rendering migration, sky/lighting, the particle
|
||
system, or the network handshake. Those are done. Don't revisit them
|
||
— even if you see something that could be 10% better. Visual
|
||
nice-to-haves and architecture second-guesses on frozen phases are
|
||
explicitly post-M7. The freeze list per milestone lives in the
|
||
milestones doc.
|
||
|
||
3. **Crossing a milestone is a textual event, not a video event.**
|
||
When a milestone's demo scenario is functionally complete, update
|
||
`2026-05-12-milestones.md` with a one-paragraph writeup describing
|
||
what works end-to-end, flip the freeze list, and update the
|
||
"currently working toward" line in this CLAUDE.md's **Current
|
||
state** section to the next milestone. Do NOT ask the user to
|
||
record a demo video — they find it pointless. The milestones doc
|
||
+ the CLAUDE.md flip ARE the milestone artifact. Phases ship;
|
||
milestones land.
|
||
|
||
4. **State both altitudes at session start.** First action of any
|
||
session: "Currently working toward [milestone]. Current phase:
|
||
[phase]. Next concrete step: [whatever]." This keeps the high-level
|
||
orientation visible alongside the immediate task and makes
|
||
mid-session drift obvious. The **Current state** section at the
|
||
top of this file is the always-current snapshot.
|
||
|
||
When reality and the milestones diverge — a phase grows beyond the
|
||
milestone's scope, a demo scenario turns out to be unreachable without
|
||
a new sub-phase, the order needs reshuffling — update the milestones
|
||
doc in the same session you discover the divergence. Same rule as the
|
||
roadmap.
|
||
|
||
## 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.
|
||
|
||
## Issue tracking — `docs/ISSUES.md`
|
||
|
||
Tactical rolling list of known bugs + small deferred features. Scope:
|
||
anything that fits in one-to-two commits; larger work stays as a Phase
|
||
in the roadmap. Maintenance rules:
|
||
|
||
1. **Start-of-session:** scan OPEN issues in `docs/ISSUES.md` — any of
|
||
them in the area you're about to touch? Plan accordingly.
|
||
2. **End-of-session:** if this session observed a defect and didn't fix
|
||
it inline, add an issue. If this session fixed an OPEN issue, move
|
||
it to **Recently closed** with the commit SHA.
|
||
3. **Commit messages:** reference issue IDs when a commit closes one
|
||
(`fix #3: periodic TimeSync parsing`). Makes `git log --grep='#3'`
|
||
return the full fix history.
|
||
4. **Promotion:** if an issue grows into multi-commit work, promote it
|
||
to a Phase in the roadmap. Close the issue as
|
||
`DONE (promoted to Phase X)` and reference the roadmap commit.
|
||
|
||
The roadmap is strategic (month-scale, phase-level). ISSUES is tactical
|
||
(week-scale, bug-level). They reference each other but don't duplicate.
|
||
|
||
## 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"`:
|
||
|
||
```powershell
|
||
$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 after a disconnect, and the duration
|
||
depends on HOW the client exited.** Two cases:
|
||
|
||
1. **Graceful close (client sent logout packet to ACE):** session clears
|
||
in ~3–5 seconds. Wait briefly between launches.
|
||
2. **Hard kill (Stop-Process, crash, force-close):** no logout packet
|
||
reached ACE. ACE keeps the session marked logged-in until its own
|
||
timeout — observed in practice at ~3+ minutes. Subsequent relaunches
|
||
fail with `live: session failed: CharacterList not received` (exit 29)
|
||
the entire time. **There is no admin command available to us to kick
|
||
the stale session.** Either wait it out, or use the graceful path
|
||
below.
|
||
|
||
**Prefer the graceful close path when ending a launch.** PowerShell's
|
||
`Stop-Process` is a hard kill — it bypasses the client's shutdown hook
|
||
which is where the logout packet would have been sent. The graceful
|
||
alternative sends WM_CLOSE so the window's close handler runs:
|
||
|
||
```powershell
|
||
$proc = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue
|
||
if ($proc) {
|
||
$proc.CloseMainWindow() | Out-Null
|
||
if (-not $proc.WaitForExit(5000)) {
|
||
# Fell through to hard-kill — session WILL be stuck on ACE.
|
||
$proc | Stop-Process -Force
|
||
}
|
||
}
|
||
Start-Sleep -Seconds 3
|
||
# ... then launch ...
|
||
```
|
||
|
||
If `WaitForExit(5000)` returns false (the client didn't exit in 5 seconds
|
||
after WM_CLOSE), the client is unresponsive and a hard kill is the only
|
||
option — accept that ACE will be unhappy for a few minutes.
|
||
|
||
**When recovering from a hard-killed session that ACE still considers
|
||
active:** the only honest answer is to wait. Don't bother retrying every
|
||
30 seconds — make a single retry attempt ~3 minutes after the kill, and
|
||
if it still fails wait another 2 minutes before trying again. The user
|
||
will likely volunteer when ACE has cleared the session if you ask.
|
||
|
||
The user has repeatedly confirmed this — don't treat exit-29-after-relaunch
|
||
as a code bug. It's a server-side session-cleanup delay whose duration is
|
||
governed by whether the previous shutdown was graceful or forced.
|
||
|
||
### 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 inbound `UpdateMotion` (guid,
|
||
stance, cmd, speed) + resulting `SetCycle` call. 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.
|
||
- `ACDREAM_REMOTE_VEL_DIAG=1` — dump per-tick / per-UM remote motion
|
||
diagnostics (`[UM_RAW]`, `[SCFAST]`, `[SCFULL]`, `[SETCYCLE]`,
|
||
`[FWD_WIRE]`, `[OMEGA_DIAG]`, `[SEQSTATE]`, `[PARTSDIAG]`,
|
||
`[VEL_DIAG]`, `[UPCYCLE]`). Heavy.
|
||
- `ACDREAM_PROBE_RESOLVE=1` — one `[resolve]` line per
|
||
`PhysicsEngine.ResolveWithTransition` call: input + target + output
|
||
position/cell, ok-vs-partial, grounded-in, contact-plane status,
|
||
wall normal if hit, **responsible entity guid**, env flag, walkable
|
||
polygon valid. Heavy (~30 Hz × every entity). Runtime-toggleable via
|
||
the DebugPanel "Diagnostics" section if `ACDREAM_DEVTOOLS=1`.
|
||
- `ACDREAM_PROBE_CELL=1` — one `[cell-transit]` line per
|
||
`PlayerMovementController.CellId` change: old → new cell, world
|
||
position, reason tag (`resolver` / `teleport`). Low volume — only
|
||
fires on actual cell crossings. Runtime-toggleable via the same
|
||
DebugPanel section.
|
||
- `ACDREAM_PROBE_PUSH_BACK=1` — emits three line types per physics
|
||
tick: `[push-back]` (per `BSPQuery.AdjustSphereToPlane` call),
|
||
`[push-back-disp]` (per `BSPQuery.FindCollisions` dispatch),
|
||
`[push-back-cell]` (per `Transition.CheckOtherCells` off-cell hit).
|
||
Heavy under motion (~100–500 lines/sec). Pair with retail's cdb
|
||
breakpoint set at `tools/cdb/a6-probe.cdb` for the A6.P1 capture
|
||
protocol. Runtime-toggleable via the DebugPanel.
|
||
- `ACDREAM_PROBE_FLAP=1` — capture probe for indoor visibility
|
||
decisions at frame boundaries. Used to converge the U.4c flap fix
|
||
(root indoor visibility at player's cell, not eye).
|
||
- `ACDREAM_PROBE_STICKY=1` — per-guid sticky-melee timeline: `[sticky]`
|
||
lifecycle lines (STICK/UNSTICK/LEASE-EXPIRE/TARGET-status teardown),
|
||
per-armed-tick steer lines (signed gap dist, applied delta, heading
|
||
delta), `[sticky-snap-skip]` at the suppressed NPC UP-snap site.
|
||
Heavy while a pack is stuck (~60 Hz × stuck count). Converged the
|
||
#171 residuals (the deep-overlap sign pin AP-82).
|
||
- `ACDREAM_CAPTURE_RESOLVE=<path>` — live capture of every player-side
|
||
`PhysicsEngine.ResolveWithTransition` call. Each call appends one
|
||
JSON Lines record with full inputs, PhysicsBody snapshot before AND
|
||
after, plus the `ResolveResult`. Filtered to `IsPlayer` mover flag
|
||
— NPC / remote DR calls don't pollute. Pairs with the trajectory
|
||
replay harness comparison tests to diff captured vs harness state
|
||
per field — the first divergence pinpoints missing apparatus state.
|
||
Capture is OFF when the env var is unset (one null-check cost per
|
||
call).
|
||
- `ACDREAM_DUMP_CELLS=<path>` / `ACDREAM_DUMP_GFXOBJS=<path>` — dump
|
||
resolved cell/GfxObj polygon tables as JSON when ids cache. Used
|
||
for harness fixture extraction.
|
||
|
||
### Outbound motion wire format (acdream → ACE)
|
||
|
||
Important quirk for cross-checking observed remote behavior. acdream's
|
||
`PlayerMovementController` + `MoveToState` builder encode motion as:
|
||
|
||
| Local input | Wire `ForwardCommand` | Wire `HoldKey` | Wire `ForwardSpeed` |
|
||
|---|---|---|---|
|
||
| W (run) | `WalkForward` (0x05) | `Run` (2) | server runRate (~2.4–2.94) |
|
||
| W + Shift (walk) | `WalkForward` (0x05) | `None` (1) | 1.0 |
|
||
|
||
ACE auto-upgrades `WalkForward + HoldKey.Run` → `RunForward (0x07)`
|
||
when relaying to remote observers. So our INBOUND parser sees
|
||
`fwd=0x07` for "remote is running." This matches retail's encoding.
|
||
|
||
When the local player toggles Shift while keeping W held (Run↔Walk
|
||
demote/promote), acdream sends a fresh `MoveToState` with the new
|
||
HoldKey + ForwardSpeed. Retail's outbound likely does the same, but
|
||
ACE's behavior on relay is uncertain — see open issues in `docs/ISSUES.md`
|
||
for the Run↔Walk cycle bug on observed retail-driven remotes.
|
||
|
||
### Visual verification workflow
|
||
|
||
1. Make a code change.
|
||
2. `dotnet build` — must be green before launch.
|
||
3. Launch (background). Give it ~8 s to reach in-world state.
|
||
4. User tests in the running client (moves, interacts, watches remote
|
||
chars). Close window when done.
|
||
5. Read `launch.log` or the task output file for diagnostic lines.
|
||
6. 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 `+Acdream`
|
||
character 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: cross-check the relevant ones
|
||
|
||
The `references/` tree holds **six** vendored projects (ACE, ACViewer,
|
||
WorldBuilder, Chorizite.ACProtocol, holtburger, AC2D). They overlap in
|
||
some areas and disagree in others. Before committing to an approach,
|
||
**cross-reference at least two of them** for the domain you're working
|
||
in — the per-domain hierarchy in the next section tells you which to
|
||
read first. A single reference can be misleading; the intersection of
|
||
the relevant references 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.
|
||
|
||
The six references:
|
||
|
||
- **`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/`** — **acdream's rendering + dat-handling
|
||
base.** WorldBuilder is not just a reference: `ObjectMeshManager` is
|
||
the production mesh pipeline, `WbMeshAdapter` is the seam, and
|
||
`WbDrawDispatcher` is the production draw path. The modern path is
|
||
**mandatory** — missing bindless throws at startup, there is no
|
||
legacy fallback. **Before re-porting any rendering or dat-handling
|
||
algorithm from retail decomp, read
|
||
`docs/architecture/worldbuilder-inventory.md` first.** The inventory
|
||
tells you what WB covers (terrain, scenery, static objects, EnvCells,
|
||
portals, sky, particles, texture decoding, mesh extraction,
|
||
visibility/culling) and what we still write ourselves (the 🔴 list:
|
||
network, physics, animation, movement, UI, plugin, audio, chat).
|
||
WorldBuilder is MIT-licensed and exact-stack with us (Silk.NET +
|
||
.NET); the divergences we've documented (e.g. WB's terrain split
|
||
formula vs retail's `FSplitNESW`) are called out in the inventory
|
||
doc.
|
||
- **`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.
|
||
|
||
- **`references/AC2D/`** — **C++ AC client emulator.** Oldest reference,
|
||
fixed-function OpenGL, but has the **real AC terrain split formula**
|
||
(`FSplitNESW` with constants `0x0CCAC033`, `0x421BE3BD`, `0x6C1AC587`,
|
||
`0x519B8F25`) which differs from WorldBuilder's physics-path formula.
|
||
Also has the complete `0xF61C` movement packet format with flag bits
|
||
and the `stMoveInfo` sequence 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. See
|
||
`docs/research/2026-04-12-movement-deep-dive.md` for the full analysis.
|
||
|
||
### 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 |
|
||
|--------|---------------|-----------|-------|
|
||
| **Any AC-specific algorithm** | **`docs/research/named-retail/`** (PDB-named decomp + verbatim retail header structs from Sept 2013 EoR build) | the existing references below | The retail client itself, fully named. 18,366 functions + 5,371 struct types + 1.4 M lines of pseudo-C in one searchable tree. Beats every other reference for "what does the real client do." Use for everything in the 🔴 list (network, physics, animation, movement, UI, plugin, audio, chat). |
|
||
| **Terrain** (split direction, height sampling, palCode, vertex position, normals) | **WorldBuilder `TerrainGeometryGenerator.cs` + `TerrainUtils.cs`** | retail decomp for cross-check | WB is acdream's terrain base. ACME's port is older/SUPERSEDED by WB. |
|
||
| **Terrain blending** (texture atlas, alpha masks, road overlays) | **WorldBuilder `LandSurfaceManager.cs`** | ACME `LandSurfaceManager.cs` (same algo, less complete) | WB is acdream's blending base. |
|
||
| **Scenery** (procedural placement: trees, bushes, rocks, fences) | **WorldBuilder `SceneryRenderManager.cs` + `SceneryHelpers.cs`** | retail decomp `CLandBlock::get_land_scenes` | WB is acdream's scenery base. Re-porting from retail decomp is what caused the edge-vertex bug. |
|
||
| **GfxObj / Setup rendering** (mesh extraction, multi-part assembly, ObjDesc) | **WorldBuilder `StaticObjectRenderManager.cs` + `ObjectMeshManager.cs`** | ACME `StaticObjectManager.cs` (includes CreaturePalette, GfxObjRemapping, HiddenParts — useful for character appearance which WB doesn't cover) | WB for static objects, ACME for character appearance. |
|
||
| **Texture decoding** (INDEX16, P8, DXT, BGRA, alpha) | **WorldBuilder `TextureHelpers.cs`** | ACME `TextureHelpers.cs`; ACViewer's `IndexToColor` is canonical for subpalette overlay | WB is acdream's decode base. |
|
||
| **EnvCell / dungeon rendering** (cell geometry, portal visibility, collision mesh) | **WorldBuilder `EnvCellRenderManager.cs` + `PortalRenderManager.cs`** | ACME `EnvCellManager.cs` (more complete for collision); ACViewer `Physics/Common/EnvCell.cs` | WB is acdream's geometry base; ACME for collision until ported. |
|
||
| **Particles / sky** (particle systems, weather, sky particles) | **WorldBuilder `SkyboxRenderManager.cs` + `ParticleEmitterRenderer.cs` + `ParticleBatcher.cs`** | retail decomp | WB is acdream's particle base. |
|
||
| **Visibility / culling** (frustum, cell visibility) | **WorldBuilder `VisibilityManager.cs` + `Frustum.cs`** | — | WB. |
|
||
| **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).
|