Single bisectable commit where the user-visible keyboard layout flips from acdream-current (W/S/A/D/Z/X) to canonical AC retail (W/X/A/D/Z/C). The InputDispatcher abstraction landed in K.1a, existing handlers cut over in K.1b, and now KeyBindings.RetailDefaults() returns the byte-precise retail preset matching docs/research/named-retail/retail-default.keymap.txt. Movement (matches AC1 muscle memory): - W/Up = MovementForward (run by default) - X/Down = MovementBackup - A/Left = MovementTurnLeft - D/Right = MovementTurnRight - Z = MovementStrafeLeft - C = MovementStrafeRight - Alt+A / Alt+Left = MovementStrafeLeft (Alt-flips-turn) - Alt+D / Alt+Right = MovementStrafeRight - LShift (Hold) = MovementWalkMode (default = run; held = walk) - Q = MovementRunLock (autorun toggle) - S = MovementStop (sets Ready stance / idle) - Space = MovementJump (hold to charge) - Y = Ready, G = Sitting, H = Crouch, B = Sleeping (postures) Selection / targeting (18 bindings on punctuation cluster): - F = SelectionPickUp, T = SelectionSplitStack, P = PreviousSelection - Backspace/Minus/Equals = closest/prev/next CompassItem - Backslash/[/] = closest/prev/next Item - Apostrophe/L/Semicolon = closest/prev/next Monster - Home = LastAttacker - Slash/Comma/Period = closest/prev/next Player - N/M = prev/next Fellow - E = SelectionExamine - R = UseSelected UI: - F1 = ToggleHelp; Shift+Ctrl+F1 = TogglePluginManager - F3 = Allegiance, F4 = Fellowship, F5 = Spellbook, F6 = SpellComponents - F8 = Attributes, F9 = Skills, F10 = World, F11 = Options (lights up the Settings panel in K.3), F12 = Inventory - Alt+1/2/3/4 = ToggleFloatingChatWindow1/2/3/4 - Esc = EscapeKey, Shift+Esc = LOGOUT - Numpad * = CaptureScreenshot Hotbar / spellbook: - 1-9 = UseQuickSlot_1..9 (hotbar) AND UseSpellSlot_1..9 (in MagicCombat scope - dormant until Phase L) - Ctrl+1-9 = UseQuickSlot_1..9 (duplicate) - Alt+5-9 = UseQuickSlot_14..18 (second bar) - 0 / Ctrl+0 = CreateShortcut Chat: - Tab = ToggleChatEntry (focus chat input; subscriber stub-TODO in K.2) - Return = EnterChatMode (send) Combat (mode-dependent, dormant - Phase L lights up): - Grave (`) = CombatToggleCombat - Insert/PgUp/Delete/End/PgDn = melee power+attack-level OR missile accuracy+aim-level OR magic spell-tab nav + cast (resolved by scope at runtime once CombatState.CurrentMode lands). - Ctrl+Insert/PgUp/Delete/PgDn = first/last spell tab + first/last spell Emotes: U = Cry, I = Laugh, J = Wave, O = Cheer, K = PointState Camera (numpad cluster + F2): - F2 / Numpad/ = CameraActivateAlternateMode - Numpad 4/6/8/2 = rotate left/right/up/down - Numpad - / + = move toward / away - Numpad 0 = ViewDefault, Numpad . = FirstPerson - Numpad 5 = LookDown, Numpad Enter = MapMode Scroll: - Mouse wheel handled by dispatcher OnScroll path - Ctrl+Up / Ctrl+Down = ScrollUp / ScrollDown Acdream debug actions relocated from F-keys to Ctrl+F-keys to avoid retail conflicts: - Ctrl+F1 = AcdreamToggleDebugPanel - Ctrl+F2 = AcdreamToggleCollisionWires - Ctrl+F3 = AcdreamDumpNearby - Ctrl+F7 = AcdreamCycleTimeOfDay - Ctrl+F8 / Ctrl+F9 = AcdreamSensitivityDown / Up - Ctrl+F10 = AcdreamCycleWeather AcdreamToggleFlyMode + AcdreamTogglePlayerMode have NO keyboard binding in retail-default. K.2 adds a DebugPanel button for fly toggle and auto-enter player mode at login. Total: 149 bindings. JSON load/save: - KeyBindings.LoadOrDefault(path): merge-over-defaults migration. Missing actions get default bindings; unknown actions in user file are skipped (preserves user customizations across action enum additions). Corrupt file warns + returns RetailDefaults without overwriting (don't blow away user's file silently). - KeyBindings.SaveToFile(path): writes with schema version=1, alpha- sorted action names, alpha-sorted modifier keys for stable diffs. - KeyBindings.DefaultPath() = %LOCALAPPDATA%/acdream/keybinds.json. GameWindow startup: - Replaces KeyBindings.AcdreamCurrentDefaults() call with KeyBindings.LoadOrDefault(KeyBindings.DefaultPath()) via a small LoadStartupKeyBindings() helper. - Logs "keybinds: loaded N bindings from <path>" so launch.log shows the source of truth at session start. Three deviations from plan: 1. LoadStartupKeyBindings() helper instead of inline initializer (field initializer can't call methods directly). 2. ToggleChatEntry subscriber is a no-op stub with TODO K.2 comment (ChatPanel doesn't expose FocusInput() yet; will add in K.2). 3. AcdreamRmbOrbitHold removed from RetailDefaults() to avoid double-binding RMB (SelectRight + RmbOrbitHold on the same chord would fire both subscribers). Chase-camera orbit will be replaced by MMB-hold mouse-look in K.2 - retail's CameraInstantMouseLook. 28 new tests: - KeyBindingsRetailTests: 19 cases pinning every retail mapping (W/X movement, Z/C strafe, Tab=ToggleChatEntry, Shift+Esc=LOGOUT, Shift+Ctrl+F1=TogglePluginManager, MovementWalkMode=Hold, Acdream debug on Ctrl+F*, hotbar number-row variants, etc). - KeyBindingsJsonTests: 9 cases (round-trip; missing file → defaults; corrupt → defaults + no-overwrite; merge-over-defaults; legacy version=0 parsing; Hold-activation preservation; unknown- action skip; DefaultPath shape). Solution total: 1162 green (243 Core.Net + 254 UI + 665 Core), 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| docs | ||
| memory | ||
| src | ||
| tests | ||
| tools | ||
| .gitignore | ||
| AcDream.slnx | ||
| CLAUDE.md | ||
| README.md | ||
acdream
A modern open-source C# / .NET 10 Asheron's Call client.
Faithful port of the retail client's behaviour to Silk.NET with a modern, plugin-friendly architecture. The code is modern; the behaviour is retail.
Status: playable pre-alpha. You can log in to an ACE server, walk and run through Dereth, see other players animate correctly, watch the day-night cycle, hear ambient audio, and take weapons out. Many systems are still stubbed or in-progress — see roadmap.
Stack
- Language: C# .NET 10
- Graphics: Silk.NET (OpenGL 4.3)
- Audio: OpenAL via Silk.NET
- Dat parsing: Chorizite.DatReaderWriter
- Networking: Custom UDP + ISAAC cipher + game-message layer, wire-compatible with ACEmulator server
What works
- Connecting to a local ACEmulator (ACE) server on
127.0.0.1:9000 - Character selection and login
- Rendering Dereth terrain with retail-correct texture blending, per-vertex lighting, and road overlays
- Static scenery (buildings, trees, scenery objects) via EnvCell walker
- Animated characters (own + remote) with walk / run / strafe / jump / turn / attack motions sourced from the retail motion tables
- Network sync with remote players — you can watch other characters animate correctly, including speeds and directional motion
- Day-night cycle driven from the retail Region dat (0x13000000) — correct DayGroup picking via the retail LCG, correct keyframe interpolation, correct per-keyframe sky-object replace
- Weather (rain/snow particles synced from the server via the retail DayGroup name)
- Sky dome, stars, moon, clouds, sun — each rendered from the retail Region's SkyObjects with texture scrolling and alpha fade
- Plugin host with live event replay-on-subscribe
What's stubbed or in-progress
- Indoor transitions (building interiors) — disabled, Phase B.3 pending
- Combat — animation works, damage math not wired
- Lightning visual — the retail PhysicsScript-driven flash is researched
but not wired (see
docs/research/2026-04-23-lightning-real.md) - TimeSync drift — we only sync calendar on login, not periodically, so acdream's in-game clock gradually drifts from retail's
- Landscape draw distance — currently
ACDREAM_STREAM_RADIUS=2(~400m) vs retail's several kilometres
See docs/plans/2026-04-11-roadmap.md for the ordered phase list.
See docs/ISSUES.md for the rolling list of known bugs + small deferred
features (tactical, bug-level; the roadmap is strategic, phase-level).
Building + running
Requires:
- .NET 10 SDK
- A retail Asheron's Call dat directory (Turbine/Microsoft property —
supply your own). Contains
client_portal.dat,client_cell_1.dat,client_highres.dat,client_local_English.dat. - A running ACE (ACEmulator) server on
127.0.0.1:9000(or override via env var)
Launch (PowerShell on Windows — bash has trouble with the apostrophe in "Asheron's Call"):
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
$env:ACDREAM_LIVE = "1"
$env:ACDREAM_TEST_HOST = "127.0.0.1"
$env:ACDREAM_TEST_PORT = "9000"
$env:ACDREAM_TEST_USER = "testaccount"
$env:ACDREAM_TEST_PASS = "testpassword"
dotnet run --project src\AcDream.App\AcDream.App.csproj -c Debug
Offline CLI dat inspector (no server needed):
dotnet run --project src/AcDream.Cli -- "C:\path\to\Asheron's Call"
Diagnostic env vars
| Variable | Effect |
|---|---|
ACDREAM_DUMP_SKY=1 |
Per-second dump of the interpolated SkyKeyframe values + per-SkyObject draw info + texture alpha histograms |
ACDREAM_DUMP_MOTION=1 |
Dump every inbound UpdateMotion + resulting SetCycle |
ACDREAM_STREAM_RADIUS=N |
Tune landblock visible-window radius (default 2 = 5×5) |
ACDREAM_NO_AUDIO=1 |
Suppress OpenAL init |
ACDREAM_DAY_GROUP=N |
Force a specific DayGroup index for A/B-testing weather presets |
ACDREAM_RUN_SKILL=N / ACDREAM_JUMP_SKILL=N |
Client-side run/jump skill (default 200) |
Layout
src/
AcDream.App/ rendering + audio + main loop (Silk.NET)
AcDream.Core/ game state, meshing, physics, sky, weather, lighting
AcDream.Core.Net/ UDP + ISAAC + game-message layer
AcDream.Cli/ offline dat-inspector console app
AcDream.Plugin.Abstractions/ plugin host interfaces
AcDream.Plugins.Smoke/ example plugin
tests/
AcDream.Core.Tests/ xUnit tests (742 passing)
AcDream.Core.Net.Tests/ network-layer tests
tools/
RetailTimeProbe/ Win32 P/Invoke ReadProcessMemory probe of
the live retail acclient.exe — dumps
TimeOfDay + sky-lighting globals so we
can compare against acdream's state
SkyObjectInspect/ dat-inspector for Region sky objects
references/ vendored read-only reference code — ACE,
ACViewer, WorldBuilder, holtburger,
AC2D, Chorizite, DatReaderWriter.
Gitignored.
docs/
architecture/ single-source-of-truth architecture doc
plans/ phase roadmaps + per-phase specs
research/ decompile-derived research, per-phase
findings, deep-dive agent reports
audit/ phase-completion audits
Development workflow
All AC-specific behaviour is ported from the decompiled retail client
(docs/research/decompiled/). The workflow is:
- Decompile first. Find the matching function in the decompiled client.
- Cross-reference. Check against ACE's C# port and ACViewer / WorldBuilder.
- Write pseudocode. Translate C to readable pseudocode first.
- Port faithfully. Translate line-by-line, preserving variable names and control flow.
- Conformance test. Add tests using golden values from retail.
- Integrate surgically. Minimise churn in the surrounding pipeline.
Guessing at AC-specific algorithms is explicitly forbidden — see
CLAUDE.md for the full workflow rationale and the list of failure
modes we've paid for in the past.
Reference repos
We cross-reference five external projects for every retail behaviour:
- ACE (ACEmulator) — authoritative server-side protocol
- ACViewer — MonoGame dat viewer; good for character appearance
- WorldBuilder — Silk.NET dat editor; matches our stack
- Chorizite.ACProtocol — clean-room C# protocol library
- holtburger — most complete non-retail client; Rust TUI, full client-side behaviour
- AC2D — C++ AC-client emulator; has the real terrain split formula and 0xF61C movement packet format
See CLAUDE.md for which reference is authoritative for which domain.
Licence
Not yet chosen. All external reference code is vendored under its own
licence; see references/*/LICENSE. The acdream source code itself is
unreleased — not yet distributed to the public. Once the licence
choice is made it will go in a top-level LICENSE file.
The AC dat files and the game's intellectual property remain the property of Microsoft / Turbine. This project does not distribute any of those files or assets — you must supply your own retail install.