Two changes, one measurement. The V6m smoke pair put GL versus Vulkan at Holtburg at 18.52% of the frame differing at tolerance 2 with MSAA off. The same stop on the same instrument now measures 9.05%, and the two populations these address are gone from the difference map rather than merely smaller. 1. THE WORLD ATLASES WERE SAMPLED WITHOUT ANISOTROPY ON VULKAN, AND WITH THE DEVICE MAXIMUM ON GL. RhiWorldTextureArray -- the backend-neutral shared object/material atlas, and the only IWorldTextureArray the Vulkan arm ever constructs -- registered its clamp and repeat slots with GpuSamplerDescription.WorldClamp/WorldRepeat as written, which carry MaxAnisotropy 1. The GL arm asks for the driver's own GL_MAX_TEXTURE_MAX_ANISOTROPY twice over: ManagedGLTextureArray sets GL_TEXTURE_MAX_ANISOTROPY on the image, and the two sampler objects its resident bindless handles are built from (OpenGLGraphicsDevice.WrapSampler/ClampSampler) set it again, which is the one that actually wins. V6i-2 knew it was asking for 1 and said so in a comment -- "the world arm that draws through these arrays is the next slice, and it is the one that can gate a filtering change visually." That slice was V6j, the gate is V7, and this is it. Retail settles the question rather than the GL arm settling it. RenderDeviceD3D::SetDefaultD3DStates (0x005a3800) loops all sixteen sampler stages and issues SetSamplerState(stage, 0xA, this->m_D3DCaps.MaxAnisotropy) at 0x005a4230. 0xA is D3DSAMP_MAXANISOTROPY and the argument is the device's reported cap, not a setting -- so "as much anisotropy as this device has" is retail's own rule, the GL arm is faithful to it, and asking for 1 diverged from retail as well as from the shipping backend. No divergence-register row is owed in either direction: this retires a Vulkan-only gap and lands on retail's value. The fix asks for a ceiling rather than reading a limit back, because the pinned RHI contract (plan section 3.3) carries no anisotropy field and is frozen. It does not need one: VulkanGpuSampler already clamps MaxAnisotropy to VkPhysicalDeviceLimits.maxSamplerAnisotropy, Vulkan guarantees that limit is at least 16 wherever the samplerAnisotropy feature is supported -- which this backend requires -- and 16 is where every desktop driver caps. The request and the GL arm's read therefore land on the same number. What it was worth, from the difference map at the same stop: the roof shingles of both Holtburg cottages, which had been dense hatching across the whole surface, and the stone courses of the near building are now black. Measured as high-frequency energy (mean absolute neighbour difference, GL versus Vulkan) the right-hand roof went from visibly blurred to a ratio of 0.999 and the wall to 1.023; every other textured region in the frame is between 0.99 and 1.02. Grazing-angle surfaces are where anisotropy is the whole difference, which is why a roof was the loudest thing in the frame. 2. THE SKY HAS TWO CLOCKS AND ONLY ONE OF THEM WAS PINNABLE. ACDREAM_DAY_GROUP and the route's AcdreamCycleTimeOfDay presses pin the Dereth date, which chooses the day group, the keyframe and the sun angle. The cloud sheet does not read that clock: SkyRenderer accumulates TexVelocityX/Y against DateTime.UtcNow minus its own construction time, by design, because retail's clouds drift with real time regardless of the date. Two launches minutes apart therefore cannot agree about where the clouds are no matter what the route does, and the V6m smoke measured the cost -- 89% of its 18.52% sat in the top 240 rows. ACDREAM_SKY_PHASE_SECONDS (RuntimeOptions.SkyAnimationPhaseSeconds -> SkyRenderer.AnimationPhaseSecondsOverride) replaces that elapsed-seconds value with a fixed one. Unset -- the default, and every ordinary run -- keeps the wall clock, so nothing a user or the offline gate sees changes. The differential gate forces it on both launches alongside MSAA and the day group; the offline gate keeps its top-280 mask, because a same-commit GL pair still has the sun to disagree about. This is instrument determinism on the same footing as ACDREAM_DAY_GROUP, not a workaround: it is one input to a UV offset, it is off by default, and no shipping path reads it. The alternative on the table was -MaskTopPixels, which would have permanently blinded the campaign's strictest instrument to the entire sky -- one of the five surfaces the offline gate already cannot see. Rows 0-32 of the Holtburg pair went from 23,090 differing pixels to 1,211, and what remains up there is roof and portal rather than cloud. WHAT THE SAME PAIR STILL SHOWS, unattributed and carried to the next commit: the distant treeline, the player and the NPCs, and the animated portal. The portal is phase and expected. The treeline is not filtering -- sharpness now matches within 5% and a shift search finds no sub-pixel offset -- and the two runs entered the world at different last-logout positions (0xC95B0001 versus 0x09040008), so the far-tier streaming history differed. That is the next thing to prove or refute. Gates. Release build green. App tests 4,133 passed / 3 skipped against the 4,132/3 baseline (one new: the sky-phase parse). GL offline pixel gate against the pre-change tree: 2.31e-05, 13 pixels of 563,200, inside the documented 9-31 band -- GL did not move. One offline Vulkan run with VK_LAYER_KHRONOS_validation proven inserted by the loader: zero validation errors, zero warnings. Full three-stop differential recorded at artifacts/v7-diff-c1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .github | ||
| .vscode | ||
| docs | ||
| memory | ||
| references | ||
| src | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| .gitmodules | ||
| AcDream.slnx | ||
| AGENTS.md | ||
| 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.
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.pakproduced byAcDream.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
GameRuntimeis hosted by both the graphical client andAcDream.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 1–3, 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
#225retains 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.datclient_cell_1.datclient_highres.datclient_local_English.dat
- A machine-local
acdream.pakbuilt 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/:
- Search the named retail pseudo-C and headers by
class::method. - Use the older Ghidra chunks only when the named oracle is insufficient.
- Cross-reference ACE and the relevant client/viewer implementation.
- Record readable pseudocode and exact constants/order.
- Port the retail mechanism into the correct modern owner.
- Add conformance, lifecycle, and failure-boundary tests.
- Run the automated gate and the appropriate connected or visual gate.
- 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.