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 989f665214 feat(CT-GF1): port retail ancestor-clip to the retained UI tree
Fixes the CT7 gate finding: on the Titles tab, the authored divider
0x10000530 escapes the Character window above its top edge at the
CT6-correct 372px mounted default (computed Y ~ -178, matching the
owner's screenshot). Retail clips child rendering to the intersected
ancestor clip-rect chain -- UIRegion::DrawHere @0x0069FA30 takes the
element's screen Box2D plus a SmartArray<Box2D> of inherited clip
rects, intersects them (the min/max clamp loop @0x0069FAA7..0x0069FB82),
and draws EraseSelf/DrawChildren/DrawSelf with the intersected rect
only when non-empty (the var_24 gate @0x0069FB8E). Our UiElement draw
walk rendered children unclipped by default, so any authored element
relying on clipping -- this divider, and the chat input row at small
window sizes (the owner's earlier "text input sticks out on resize"
report) -- became a visible artifact.

Mechanism (element-level, reusing the existing clip-rect-stack
infrastructure in UiRenderContext.PushClip/PopClip):

- UiElement.ClipsChildren now defaults to TRUE for every element
  (was an opt-in used only by UiScrollablePanel/UiItemList). Each
  element's children draw AND hit-test clipped to the intersection
  of its own rect with the inherited ancestor clip; an element
  positioned outside its parent's box silently disappears, matching
  retail's non-empty-intersection gate. HitTest's existing early
  bounds check already implemented this shape for ClipsChildren=true
  elements -- flipping the default aligns hit-testing with the new
  draw-clip default in one property, per the plan's own point 4.

- UiElement.ExpandsClipForPopup (default false) is the one opt-out:
  retail spawns a menu popup as a SEPARATE top-level region
  (UIElement_Menu::MakePopup), clipped only by the screen; acdream
  draws UiMenu's popup inline from the owning button in a second
  traversal (OnDrawOverlay, pre-existing -- its own doc comment
  already says "regardless of this element's position in the tree").
  DrawOverlays now resets the accumulated clip to unbounded
  (UiRenderContext.PushClipUnbounded, sharing the existing clip
  stack) for exactly the OnDrawOverlay call of an opted-in element.
  UiMenu overrides ExpandsClipForPopup=>true, paired with
  ClipsChildren=>false so its own out-of-bounds OnHitTest union (the
  popup occupies ly<0 or ly>=Height depending on open direction)
  stays reachable through the same early-bounds gate that now
  defaults on for every other element.

Opt-out audit (grep for OnDrawOverlay overrides + negative/overflow
OnDraw coordinates across src/AcDream.App/UI): UiMenu's popup is the
ONLY OnDrawOverlay override client-wide, so it is the only element
needing ExpandsClipForPopup. RetailTooltipPresenter's popup and
UiRoot's drag ghost both already escape structurally -- the tooltip
mounts as an ordinary UiRoot CHILD (sibling of every window, clipped
only by the canvas), and the drag ghost is drawn directly by UiRoot
outside the tree entirely -- neither needed a code change, both are
covered by new tests proving the invariant. UiResizeGrip and
UiNineSlicePanel's frame/bevel draw entirely within their own
[0,Width]x[0,Height] (grip flush at the window's own edges; the
window's own Width/Height already represents the OUTER frame
including its 5px bevel, so its ClipsChildren push already covers
the frame's own content children correctly -- no negative insets
found). UiScrollbar draws entirely within its own bounds (confirmed
by reading OnDraw).

Hit-testing: aligned with the new default via the single
ClipsChildren flip (see above); UiMenu's own opt-out override keeps
its popup hit-test union working, verified by the full UiMenuTests
suite staying green.

Divergence register: AD-113 filed for the ExpandsClipForPopup
adaptation (inline popup drawing vs retail's separate top-level
region).

Fixed two pre-existing test-harness gaps the new default surfaced
(both real bugs in the harnesses, not workarounds around the fix):
- ChatLayoutConformanceTests' bottom-right-grip grow test read a
  STALE (pre-shrink) grip screen position because it drove two resize
  gestures back-to-back with no intervening Draw pass -- the only
  place UiElement.ApplyAnchor/LayoutPolicy.Apply run. A real frame
  draws every tick, so production never hits this; the test now
  inserts a real DrawSelfAndChildren pass between the two gestures,
  matching a real frame boundary.
- VendorUiControllerTests' hand-built Items/Buying/Selling page
  containers were left at their bare 0x0 UiElement default (the
  harness never runs a real DAT-driven layout pass) -- harmless
  before ancestor clipping existed, but now hides every child of an
  unsized page. Sized them to the window's own content root, matching
  production's shape (a tab page fills the window body).

Tests (all confirmed as genuine regression pins by temporarily
reverting the relevant default/override and observing the exact
predicted failure, then reverting back):
- CharacterTitlesControllerTests.TitlesPage_Divider_ClipsAwayAtThe
  CT6Default_AndAppearsWhenTheWindowGrowsTaller: the literal gate
  repro against the real character_2100002E.json fixture through
  RetailWindowFrame.Mount at the CT6 372px default -- the divider
  renders nothing (computed Y ~ -173, matching the owner's ~-178);
  growing the window to 600px renders it at its authored spot.
- ChatLayoutConformanceTests.ResizingTheWindowSmall_NoInputRowQuad
  RendersOutsideTheWindowRect: no input-row quad escapes the chat
  window rect at three small sizes (300x100 sanity control,
  120x40/80x30 genuine pre-fix overflow -- verified failing without
  the fix at Y=38/55 past the window edge).
- UiAncestorClipTests (new file): the core mechanism against plain
  synthetic elements (culled-outside / clipped-at-the-edge / hit-test
  parity), UiMenu's popup escaping a tiny owning window (and staying
  clipped while closed), and the tooltip's structural immunity
  (mounts as a UiRoot sibling, unaffected by a tiny ancestor window).

Verification: full solution build green; hermetic suite green
(--filter "Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&
Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&
Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure",
14,000+ tests across every project); InstalledDat lane green
(ACDREAM_RUN_INSTALLED_DAT_TESTS=1, Status!=KnownFailure,
205+34+3+172 tests). CharacterTitlesControllerTests' existing suite
and the full UiMenuTests/UiScrollbarTests suites are unaffected.

src/AcDream.App/UI/UiRoot.cs carries an unrelated, pre-existing
uncommitted owner probe (ACDREAM_PROBE_UI_HOVER) -- untouched by
this change and deliberately left out of this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 06:29:15 +02:00
.gitea/workflows ci: put the downloads in the 'latest' release, not just the pointer 2026-08-19 16:25:48 +02:00
.github ci: make GitHub workflows manual only 2026-08-18 17:50:50 +02:00
.vscode ci: add GitHub Agentic Workflows scaffolding + daily hygiene assessment 2026-05-22 23:31:13 +02:00
assets/icons fix(app): apply the window icon from Load, not beside Window.Create 2026-08-20 15:13:29 +02:00
docs feat(CT-GF1): port retail ancestor-clip to the retained UI tree 2026-08-25 06:29:15 +02:00
memory fix(platform): Campaign LA LA0 review fixes — CI Linux lanes, arch doc, self-guard 2026-08-14 15:30:04 +02:00
references chore(submodule): advance WB to acdream-fix-floor-rendering 2026-05-19 13:23:19 +02:00
samples feat(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +02:00
src feat(CT-GF1): port retail ancestor-clip to the retained UI tree 2026-08-25 06:29:15 +02:00
tests feat(CT-GF1): port retail ancestor-clip to the retained UI tree 2026-08-25 06:29:15 +02:00
tools docs(ace-mods): SetTime loads via @mod find, no ACE restart needed 2026-08-23 14:34:43 +02:00
.gitattributes ci: add GitHub Agentic Workflows scaffolding + daily hygiene assessment 2026-05-22 23:31:13 +02:00
.gitignore feat: mosswart client icon and Asheron's Call-inspired launcher icon 2026-08-20 14:42:10 +02:00
.gitmodules phase(N.0): wire up WorldBuilder fork as submodule + project refs 2026-05-08 08:51:49 +02:00
AcDream.slnx feat(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +02:00
AGENTS.md docs: sync UI/rendering instructions with Campaign V reality 2026-08-01 22:56:56 +02:00
analyze_flap_live.py diag(render): flap re-diagnosed as portal-flood re-clip DRIFT; physics + camera REFUTED 2026-06-08 11:21:46 +02:00
CLAUDE.md fix #435 (part 2, closes it): attribute the unowned probes — delete 7, reclassify 8, restore 1 2026-08-24 12:32:42 +02:00
Directory.Build.props build: make release restore reproducible 2026-08-18 10:29:00 +02:00
Directory.Packages.props build: make release restore reproducible 2026-08-18 10:29:00 +02:00
find_burst.py diag(render): flap re-diagnosed as portal-flood re-clip DRIFT; physics + camera REFUTED 2026-06-08 11:21:46 +02:00
global.json ci: add bounded complete release gate 2026-08-18 09:09:38 +02:00
launch-a6-issue98-capture.ps1 docs(research): A6.P3 #98 — comparison harness findings + neighborhood fixtures 2026-05-23 20:12:43 +02:00
launch-a6-issue98-cottage-gfxobj-dump.ps1 test(phys): A6.P3 #98 — comparison harness reproduces cottage-floor cap 2026-05-23 20:44:50 +02:00
launch-a6-issue98-polydump.ps1 docs(research): A6.P3 #98 — comparison harness findings + neighborhood fixtures 2026-05-23 20:12:43 +02:00
launch-flap-capture.ps1 diag(render): flap re-diagnosed as portal-flood re-clip DRIFT; physics + camera REFUTED 2026-06-08 11:21:46 +02:00
launch-flap-churn.ps1 diag(render): launch-flap-churn.ps1 — Phase 1 portal-churn pin capture script 2026-06-08 12:56:44 +02:00
launch-flap-verify.ps1 docs(render): FLAP settled by live-retail measurement — full retail port DECIDED (Option A) + exhaustive handoff 2026-06-08 16:19:34 +02:00
NOTICE.md chore(O-T1): create Core/Rendering/Wb directory + NOTICE.md attribution 2026-05-21 14:59:56 +02:00
NuGet.Config build: make release restore reproducible 2026-08-18 10:29:00 +02:00
README.md docs: reconcile project state and refresh README 2026-07-27 12:53:31 +02:00

acdream

A modern open-source C# / .NET 10 Asheron's Call client.

acdream ports the observable behaviour of the September 2013 retail client to Silk.NET and a modern, plugin-friendly architecture. The code is modern; the behaviour is retail.

Status: playable pre-alpha. M3, “Cast a spell,” landed on 2026-07-21 and M4, “Live in the world,” is active. The graphical client supports the connected combat, magic, movement, portal, inventory, loot, and retained-UI loops used by the current test characters. The presentation-independent GameRuntime and the Linux/Windows multi-session headless host are complete. Native Linux graphics are intentionally parked at the L1 capability checkpoint; Windows is the currently validated graphical platform.

The documentation map is the entry point for current milestones, roadmap state, architecture, issues, retail divergences, research, and durable project memory.

Technology

  • Runtime: C# and .NET 10
  • Graphics: Silk.NET, OpenGL 4.3 core, bindless textures, shader draw parameters, SSBOs, and multi-draw indirect
  • Audio: OpenAL through Silk.NET
  • Content: retail DAT files plus a machine-local, memory-mapped acdream.pak produced by AcDream.Bake
  • Networking: custom UDP, ISAAC cipher, and game-message layers compatible with ACEmulator
  • UI: retained retail gameplay UI plus opt-in ImGui developer tools
  • Automation: the same presentation-independent GameRuntime is hosted by both the graphical client and AcDream.Headless

The modern renderer is mandatory. There is no legacy renderer fallback. Startup reports an actionable error if the required OpenGL capabilities are missing.

What works

  • ACE login, character selection, world entry, chat, client commands, reconnect, and graceful logout.
  • Outdoor, building, cellar, and dungeon streaming with prepared terrain, scenery, buildings, EnvCells, collision, portal visibility, sky, fog, lighting, audio, and day/night presentation.
  • Local and observed movement, animation, jumping, selection, radar, combat stances, melee, bows, crossbows, spell projectiles, death, corpses, chests, and looting.
  • Inventory bags, stable server ordering, stack splitting, ground drops, paperdoll equipment, weapon switching, quick bars, item use, cooldowns, and giving items to NPCs.
  • Retail-style retained UI for vitals, chat, toolbar, inventory, character, attributes, skills, spellbook, components, effects, combat/spell/jump bars, radar/compass, dialogs, external containers, and assessment.
  • Complete end-of-retail spell catalog, learned and favorite spells, component preflight, connected casts, enchantments, DAT-driven projectiles and effects, recall, portal-space travel, Hidden/UnHide, and remote materialization.
  • One presentation-independent runtime owner for session, entities, objects, inventory, character state, selection, interactions, combat, magic, movement, physics, projectiles, world environment, and portal transit.
  • A no-window Windows/Linux host with deterministic bot commands/events, shared immutable content, multi-session scheduling, isolation, reconnect, resource telemetry, and tested 1/5/10/30-session ownership.
  • Plugin loading, shared command/input abstractions, retained markup panels, and permanent ImGui developer tools behind ACDREAM_DEVTOOLS=1.

Current boundaries

  • The active M4 prelude is world interaction completion. Slices 13, including assessment and its final formula/icon/layout correction, are user-accepted. Equipped-child picking and vendor browse/buy/sell are the next uncompleted slices.
  • Issue #225 retains the lifestone/particle shared-alpha visual comparison. Its connected lifetime and performance routes already pass.
  • Narrow carried behaviour debt includes issue #153 (an unstreamed far-teleport edge), issue #116 (slide feel), issue #235 (30 Hz capped/RDP jump presentation), and the live temporary-stopgap rows in the retail divergence register.
  • Native Linux graphics are deferred. L0 portability and L1 backend/capability reporting are implemented; WSLg reaches the GPU through Mesa D3D12 but does not expose mandatory GL_ARB_bindless_texture. Resume with a supported physical Linux AMD/NVIDIA driver before beginning later Slice L work.
  • Advanced vendor/trade/crafting/social surfaces and larger M4 quest, character-creation, and emote bodies remain roadmap work.

Prerequisites

  • .NET 10 SDK
  • Your own retail Asheron's Call DAT directory containing:
    • client_portal.dat
    • client_cell_1.dat
    • client_highres.dat
    • client_local_English.dat
  • A machine-local acdream.pak built from those DATs
  • A running ACE server for connected play; the examples use 127.0.0.1:9000
  • For the graphical client, a driver exposing the mandatory modern OpenGL capabilities

The project does not distribute Microsoft/Turbine DAT files or derived prepared packages.

Build and test

dotnet restore AcDream.slnx
dotnet build AcDream.slnx -c Release
dotnet test AcDream.slnx -c Release --no-build

The current baseline is a successful Release build with 8,826 passing tests and 5 intentional skips. The build currently reports 17 test-project warnings tracked by #228; production compilation has zero errors.

Prepare content

Production rendering and collision use the validated prepared package rather than decoding world meshes on the frame path:

dotnet run --project src\AcDream.Bake\AcDream.Bake.csproj -c Release -- `
  --dat-dir "C:\Games\Asheron's Call" `
  --out "C:\Games\Asheron's Call\acdream.pak"

A complete package is approximately 30 GB. It is machine-local and must not be committed. ACDREAM_PAK_PATH overrides the default <DAT directory>\acdream.pak.

Run the graphical client

$env:ACDREAM_DAT_DIR   = "C:\Games\Asheron's Call"
$env:ACDREAM_PAK_PATH  = "C:\Games\Asheron's Call\acdream.pak"
$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"
$env:ACDREAM_RETAIL_UI = "1"

dotnet run --project src\AcDream.App\AcDream.App.csproj -c Release

The DAT directory can instead be supplied as the first positional argument:

dotnet run --project src\AcDream.App\AcDream.App.csproj -c Release -- `
  "C:\Games\Asheron's Call"

Run a headless session

AcDream.Headless loads no App, UI, OpenGL, native-window, or audio assembly. Create a version-1 configuration such as bot.json:

{
  "version": 1,
  "process": {
    "content": {
      "datDirectory": "/opt/ac",
      "preparedAssetPath": "/opt/ac/acdream.pak"
    }
  },
  "sessions": [
    {
      "id": "bot-1",
      "endpoint": { "host": "127.0.0.1", "port": 9000 },
      "account": "testaccount",
      "character": { "index": 0 },
      "policy": { "id": "idle" },
      "credential": {
        "provider": "environment",
        "reference": "ACDREAM_BOT_PASSWORD"
      }
    }
  ]
}

Then validate and run it:

export ACDREAM_BOT_PASSWORD='testpassword'
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- \
  validate --config bot.json
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- \
  run --config bot.json

For a single local session, run also accepts --user <account> --password <password>. Add uniquely identified session entries and credential references for a multi-session process. Available built-in policies are idle, lifecycle-smoke, observer-movement, and portal-route-smoke.

Useful startup options

Variable Effect
ACDREAM_DAT_DIR Retail DAT directory
ACDREAM_PAK_PATH Prepared package path; defaults to <DAT dir>/acdream.pak
ACDREAM_LIVE=1 Enable connected mode
ACDREAM_TEST_HOST / ACDREAM_TEST_PORT ACE endpoint
ACDREAM_TEST_USER / ACDREAM_TEST_PASS Graphical-client credentials
ACDREAM_RETAIL_UI=1 Enable the retained retail gameplay UI
ACDREAM_DEVTOOLS=1 Enable ImGui developer tools
ACDREAM_NO_AUDIO=1 Suppress OpenAL initialization
ACDREAM_UNCAPPED_RENDER=1 Disable normal frame pacing for diagnostics
ACDREAM_DISPLAY_PROTOCOL=auto|x11|wayland Select the Linux GLFW backend
ACDREAM_DAY_GROUP=N Force a day-group index for weather/lighting comparisons
ACDREAM_STREAM_RADIUS=N Legacy override over configured streaming radii
ACDREAM_DUMP_SKY=1 Dump sky interpolation and draw diagnostics
ACDREAM_DUMP_MOTION=1 Dump inbound movement and motion-cycle decisions

Additional diagnostic and budget controls are documented beside their typed owners and in the linked research plans; they are not stable user settings.

Repository layout

src/
  AcDream.Runtime/              presentation-independent GameRuntime
  AcDream.App/                  graphical host, retained UI, renderer, audio
  AcDream.Headless/             Windows/Linux no-window multi-session host
  AcDream.Core/                 retail gameplay, movement, physics, world logic
  AcDream.Core.Net/             UDP, ISAAC, protocol and message routing
  AcDream.Content/              GL-free DAT and prepared-package content
  AcDream.Bake/                 offline acdream.pak builder
  AcDream.Cli/                  offline DAT inspector
  AcDream.UI.Abstractions/      shared UI/input models and contracts
  AcDream.UI.ImGui/             developer-tool presentation
  AcDream.Plugin.Abstractions/  BCL-only plugin contracts
  AcDream.Plugins.Smoke/        example plugin

tests/
  AcDream.*.Tests/              layer-matched xUnit projects

docs/
  README.md                     documentation authority and current map
  architecture/                ownership, structure, divergence, WB inventory
  plans/                       milestone, roadmap, and execution plans
  research/                    retail pseudocode, traces, fixtures, evidence
  audit/                       completion and conformance audits

memory/                        durable engineering references
references/                    gitignored external reference repositories

Development workflow

All AC-specific behaviour starts from the named retail oracle in docs/research/named-retail/:

  1. Search the named retail pseudo-C and headers by class::method.
  2. Use the older Ghidra chunks only when the named oracle is insufficient.
  3. Cross-reference ACE and the relevant client/viewer implementation.
  4. Record readable pseudocode and exact constants/order.
  5. Port the retail mechanism into the correct modern owner.
  6. Add conformance, lifecycle, and failure-boundary tests.
  7. Run the automated gate and the appropriate connected or visual gate.
  8. Update architecture, roadmap, divergences, and durable memory with the same change.

Guessing at AC-specific algorithms is forbidden. See AGENTS.md, CLAUDE.md, and the architecture guide for the full rules.

Reference projects

  • ACE / ACEmulator: authoritative server and protocol behaviour
  • ACViewer: character appearance and DAT presentation cross-check
  • WorldBuilder: extracted Silk.NET DAT/rendering foundation
  • Chorizite.ACProtocol: clean-room protocol reference
  • holtburger: broad non-retail client behaviour reference
  • AC2D: terrain and movement-packet cross-checks

The retail binary/decomp remains the behavioural oracle when references disagree.

Licence and game assets

The acdream source has not yet been assigned a top-level licence and is not ready for public redistribution. External reference code retains its own licence.

Asheron's Call DAT files, art, names, and other game assets remain the property of Microsoft/Turbine. This repository does not distribute them; users must supply their own retail installation.