Live bug (2026-07-03, user door test): a server MoveTo armed (movingTo=True) but the local body never moved; the retail observer saw ACE walk the server-side player to the door; the next local input reasserted the stale position (rubber-band). Log evidence: the player's pending_motions queue was non-empty on 92/94 MotionDone pops (remotes: 0/40) - MoveToManager's retail wait-for-anims gate (BeginTurnToHeading's MotionsPending check) never opened. ROOT CAUSE (found via a production-faithful repro test - the original suite force-drained the queue, masking it): the R3 port MISIDENTIFIED retail's StopCompletely_Internal body. It is NOT a physics velocity zero - CPhysicsObj::StopCompletely_Internal (0x0050ead0) tailcalls CPartArray::StopCompletelyInternal (0x00518890) which is MotionTableManager::PerformMovement(type 5): an ANIMATION-side full stop whose UNCONDITIONALLY-queued pending_animations entry is the completable partner of the A9 pending_motions node CMotionInterp::StopCompletely enqueues (raw @00527e90 between the state writes and add_to_queue). Without the dispatch, every StopCompletely (login/teleport SetPosition + every MoveToManager PerformMovement head) left an orphan node with no completion - the queue never drained at idle. (The R3 code comment claimed a register row for the stand-in; none was ever added - porting the real mechanism closes that violation without a row.) Fix set (all retail-anchored): - IInterpretedMotionSink gains StopCompletely() (default true = the null-sink posture); MotionTableDispatchSink routes it to the R2 manager's already-ported type-5 op (StopObjectCompletely + Ready-sentinel entry) + TurnStopped for the ObservedOmega seam. - MotionInterpreter.StopCompletely calls DefaultSink?.StopCompletely() at the exact retail slot (after the state writes, before add_to_queue); the immediate set_velocity(Zero) is kept as the documented physics-effect stand-in. - PlayerMovementController ticks CheckForCompletedMotions after UseTime - retail's per-tick CPartArray::HandleMovement slot (0x00517d60 = MotionTableManager::UseTime = CheckForCompletedMotions tailcall, called every tick from UpdateObjectInternal @005159a4). NOTE: a first-cut per-tick apply_current_movement pump was tried and REVERTED - retail's apply callers are all event-driven (hold-key, HitGround/LeaveGround, ReportExhaustion), not per-tick; the remote block's per-tick apply is a pre-existing adaptation outside this fix's scope. - The P1 autonomous-store family, completing the pin's port shape (the gate landed in V5; the STORE side was missing): (a) the unpack store (00509730: last_move_was_autonomous = wire byte) in the GameWindow player branch - local player only for now (remote interps have no WeenieObj, which A3 reads as IsThePlayer; storing a remote's autonomous byte would mis-route their per-tick apply onto the raw branch); (b) input edges store =1 at the controller edge block - retail's CPhysicsObj::DoMotion @00510030 / StopMotion @005100e0 / TakeControlFromServer @006b32f4 input-boundary stores; (c) section 2's per-frame velocity write now PRESERVES the flag (V5's first cut stamped it per frame, the wrong altitude - retail stores it at event boundaries only); (d) InstallSpeculativeTurnToTarget stores false (models the wire mt-6's unpack store; part of the AP-23 adaptation) so the event-driven applies (e.g. HitGround mid-moveto) route interpreted and cannot clobber the manager's dispatched motions with idle raw state. Tests: new ServerMoveToPosition_ProductionCompletionFeed_WalksToArrival drives the controller under the PRODUCTION completion feed (sequencer.Advance -> MotionDoneTarget) instead of force-draining - this is the test that reproduced the live wedge (queue stuck at the two A9 Ready orphans) and now proves the drain. Existing cutover tests mirror the wire-path autonomous store before PerformMovement. Full suite green: 3,957. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .github | ||
| .vscode | ||
| docs | ||
| memory | ||
| references | ||
| src | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| .gitmodules | ||
| AcDream.slnx | ||
| analyze_flap_live.py | ||
| CLAUDE.md | ||
| find_burst.py | ||
| launch-a6-issue98-capture.ps1 | ||
| launch-a6-issue98-cottage-gfxobj-dump.ps1 | ||
| launch-a6-issue98-polydump.ps1 | ||
| launch-flap-capture.ps1 | ||
| launch-flap-churn.ps1 | ||
| launch-flap-verify.ps1 | ||
| NOTICE.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.