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 6f1971aa9c fix(app): Phase 9.2 — enable back-face cull in translucent pass
The user reported the lifestone crystal (AlphaBlend part 3 of the
4-part 0x020002EE setup) rendered with one side consistently missing
— looked like "a box with one side missing, you can see into it"
while the whole thing rotated.

Isolated via experiment: routing the crystal through the opaque pass
(no blending, depth write on) produced a whole solid shape. Routing
it through the Phase 9.1 translucent pass (blending on, depth write
off) produced the hole. Mesh build was eliminated as the cause.

Root cause: our translucent pass matched WorldBuilder's state
(SrcAlpha/OneMinusSrcAlpha, DepthMask(false)) but NOT its culling
state. WorldBuilder enables GL_CULL_FACE with per-batch CullMode
(references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/
BaseObjectRenderManager.cs:361-365). Without face culling, the 58
triangles of the closed crystal shell drew in dict-iteration order;
back faces that happened to draw AFTER front faces composited over
them because depth-write-off meant nothing recorded depth within the
translucent set. One face of the crystal ended up permanently
overwritten by its own backside.

Fix: in pass 2 (translucent) enable GL_CULL_FACE with GL_BACK and
CCW front-face winding. Our mesh builder emits pos-side triangles as
(0, i, i+1), which is CCW in standard OpenGL conventions, so GL_BACK
correctly drops the inward-facing side. Back-face culling is disabled
again after pass 2 so subsequent renderers (terrain etc.) see the
default state.

Known limitation: neg-side polys on translucent surfaces — which my
pos/neg mesh-build fix would have emitted with reversed winding —
now get culled in the translucent pass. AC rarely uses double-sided
polygons on translucent surfaces so this is acceptable, and the
opaque pass still renders them correctly. A future Phase 9.3 can
track CullMode per sub-mesh and draw double-sided translucents with
GL_NONE if it turns out to matter.

Also strips the Portal/Lifestone [DIAG] spawn dump that served as
one-shot evidence gathering during the investigation.

194 tests green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:18:42 +02:00
docs/plans docs(plan): roadmap with every observed defect mapped to a phase 2026-04-11 18:30:55 +02:00
src fix(app): Phase 9.2 — enable back-face cull in translucent pass 2026-04-11 21:18:42 +02:00
tests feat(net): Phase 6.7 — parse UpdatePosition (0xF748) into PositionUpdated event 2026-04-11 20:37:32 +02:00
.gitignore chore: phase 0 — skeleton + dat asset inventory 2026-04-10 09:02:56 +02:00
AcDream.slnx feat(net): AcDream.Core.Net scaffold + ISAAC keystream (Phase 4.1) 2026-04-11 14:14:28 +02:00
CLAUDE.md feat(net+app): SubPalette overlays applied to palette-indexed textures (Phase 5b) 2026-04-11 16:30:08 +02:00
README.md chore: phase 0 — skeleton + dat asset inventory 2026-04-10 09:02:56 +02:00

acdream

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

Status: pre-alpha, not playable. Phase 0 only — dat file asset inventory.

Stack: .NET 10, Chorizite.DatReaderWriter for dat parsing. Silk.NET + Avalonia planned for rendering/UI (not yet wired up).

Requires: A retail Asheron's Call install (Turbine/Microsoft property — supply your own). Set ACDREAM_DAT_DIR environment variable to the directory containing client_portal.dat, client_cell_1.dat, client_highres.dat, and client_local_English.dat, or pass it as the first CLI argument.

Layout

  • src/AcDream.Cli/ — console app that dumps asset counts from a dat directory
  • references/ — local read-only reference material (ACE, ACViewer, WorldBuilder, DatReaderWriter, holtburger, retail AC install). Gitignored.

Run

dotnet run --project src/AcDream.Cli -- "C:\path\to\Asheron's Call"

Or set ACDREAM_DAT_DIR and run without args.