Modern open-source C# .NET 10 Asheron's Call client. Faithful port of retail client behaviour to Silk.NET with a plugin API.
Find a file
Erik 9a2839dfe8 Merge branch 'feature/phase-c1-particles' — Phase C.1 PES particles + sky-pass refinements
Phase C.1 ships the retail-faithful PES particle pipeline plus a set of
sky-pass refinements that landed alongside it (Translucent+ClipMap blend,
raw-Additive fog-skip, sampler-object wrap selection). Three feature
commits:

  ec1bbb4 feat(vfx): Phase C.1 — PES particle renderer + post-review fixes
  3d21c13 refactor(sky): replace per-frame wrap-mode mutation with persistent samplers
  6d159d9 docs(roadmap): mark Phase C.1 shipped

What landed
===========

Particle data layer (retail ports cited in commits):

  - ParticleEmitterInfo unpack with all 13 ParticleType motion
    integrators (Particle::Init 0x0051c930, Particle::Update 0x0051c290).
  - PhysicsScriptRunner with hook scheduling + CallPES self-loop
    semantics (FUN_0051bed0..bfb0 family).
  - ParticleHookSink translates CreateParticle / DestroyParticle /
    StopParticle / CallPES hooks into emitter spawn/stop calls.
  - EmitterDescRegistry resolves dat ParticleEmitter records to
    runtime descriptors. DAT emitters do NOT default additive — blend
    state is derived from the particle GfxObj surface flags.
  - AttachLocal (is_parent_local=1) follows the live parent each frame
    via ParticleSystem.UpdateEmitterAnchor / ParticleHookSink
    .UpdateEntityAnchor — matches retail
    ParticleEmitter::UpdateParticles 0x0051d2d4.
  - ParticleSystem.EmitterDied lets the sink prune dead per-entity
    handle tracking so naturally-expired emitters don't leak.

Particle GL renderer:

  - Instanced billboard quads with material-derived blend per particle.
  - Global back-to-front sort (across textures + blend modes).
  - Bounding-box → axis/size dispatch picking the largest two
    dimensions for non-billboard particles.
  - Point-sprite degrade detection via DegradeMode == 2.
  - C-vector orientation for ParabolicLVGAGR / LVLALR / GVGAGR.

Sky-pass refinements (most landed earlier on feature/sky-fixes; the
C.1 worktree adds the last few):

  - Translucent + ClipMap forces alpha-blend for cloud sheet
    0x08000023 (matches D3DPolyRender::SetSurface 0x0059c4d0 branch
    at decomp line 425246).
  - Raw-Additive fog-skip via uApplyFog uniform (matches 0x0059c882).
  - Per-keyframe SkyObjectReplace Translucency / Luminosity /
    MaxBright divided by 100 (raw dat is percent, shader expects
    fraction).
  - Bit 0x01 pre/post-scene split (matches GameSky
    ::CreateDeletePhysicsObjects 0x005073c0 routing).
  - Setup-backed (0x020xxxxx) sky objects via SetupMesh.Flatten —
    earlier code dropped these silently.
  - Persistent GL sampler objects (Wrap + ClampToEdge) replace
    per-frame TexParameter mutation. Ported from WorldBuilder
    (Chorizite.OpenGLSDLBackend/OpenGLGraphicsDevice.cs:115-132).
  - Post-scene Z-offset (-120m) gated on (Properties & 4) != 0 &&
    (Properties & 8) == 0 per GameSky::UpdatePosition 0x00506dd0
    instead of firing on every post-scene SkyObject.

Sky-PES playback intentionally disabled
=======================================

A 2026-04-28 named-retail recheck disproved the original C.1
sky-PES premise. SkyDesc::GetSky (0x00501ec0) copies
SkyObject.default_pes_object into CelestialPosition.pes_id, but
GameSky::CreateDeletePhysicsObjects, MakeObject, and UseTime never
read the field. The experimental sky-PES path remains gated behind
ACDREAM_ENABLE_SKY_PES=1 for dat archaeology only — do not
reintroduce per-SkyObject PES playback in the normal render path
without new decompile evidence.

Tests
=====

dotnet build green, dotnet test green: 695 + 393 + 243 = 1331 passed
(up from 1325). New tests:

  - UpdateEmitterAnchor_AttachLocal_ParticlePositionFollowsLiveAnchor
  - UpdateEmitterAnchor_AttachLocalCleared_ParticleFrozenAtSpawnOrigin
  - EmitterDied_FiresOncePerHandle_AfterAllParticlesExpire
  - Birthrate_PerSec_EmitsOnePerTickWhenIntervalElapsed
  - UpdateEntityAnchor_WithAttachLocal_MovesParticleToLiveAnchor
  - EmitterDied_PrunesPerEntityHandleTracking

Visual verification
===================

Sky / cloud / weather: confirmed by the user during phase development
(pink clouds restored, post-scene rain cylinder Z gated, no aurora
blobs on the skybox). Sampler refactor visually verified as a no-op.

Deferred to Phase C.1.5
=======================

Wiring entity-attached emitters to retail effect IDs:

  - Portal swirls (currently rotating-black-disk placeholder).
  - Chimney smoke / fireplace flames.
  - Spell-cast effect emitter spawns from animation hooks.

The ParticleHookSink wiring is ready; only the entity-side
identification of which retail effect ID belongs to each weenie
class is deferred. File a follow-up issue if needed.
2026-04-29 08:15:14 +02:00
docs docs(roadmap): mark Phase C.1 shipped 2026-04-29 08:14:21 +02:00
memory docs(workflow): align CLAUDE.md + memory + roadmap with named-retail foundation 2026-04-25 17:36:53 +02:00
src refactor(sky): replace per-frame wrap-mode mutation with persistent samplers 2026-04-29 08:08:26 +02:00
tests feat(vfx): Phase C.1 — PES particle renderer + post-review fixes 2026-04-28 22:47:11 +02:00
tools feat(sky): load Setup-backed (0x020xxx) sky objects via SetupMesh.Flatten 2026-04-27 23:24:09 +02:00
.gitignore chore: ignore .worktrees/ for isolated feature work 2026-04-26 14:58:59 +02:00
AcDream.slnx feat(ui): AcDream.UI.ImGui backend — Hexa.NET.ImGui + Silk.NET input bridge 2026-04-25 00:29:09 +02:00
CLAUDE.md feat(ui): #25 Phase K.3 — Settings panel + click-to-rebind + Phase K shipped 2026-04-26 09:44:56 +02:00
README.md docs: add docs/ISSUES.md tactical issue tracker + CLAUDE.md rules 2026-04-25 00:08:15 +02:00

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:

  1. Decompile first. Find the matching function in the decompiled client.
  2. Cross-reference. Check against ACE's C# port and ACViewer / WorldBuilder.
  3. Write pseudocode. Translate C to readable pseudocode first.
  4. Port faithfully. Translate line-by-line, preserving variable names and control flow.
  5. Conformance test. Add tests using golden values from retail.
  6. 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.