sky(phase-8): retail-faithful night sky + README refresh

Iteration on the sky rendering pipeline to restore stars/moon visibility
at night and fix washed-out grey daytime clouds. Key fixes:

* sky.frag: disable fog-mix on sky meshes. Retail's keyframe FogEnd
  (0..400m at midnight, up to 2400m during day) is calibrated for
  terrain; sky meshes are authored at radii 1050-14271m which sits
  past FogEnd universally, causing every sky pixel to saturate to
  fogColor (dark navy). Stars, moon, dome texture all got
  obliterated. The horizon-glow trade-off is noted in the shader
  comment; research item to find retail's sky-specific fog range
  later.

* SkyRenderer + sky.frag: promote rep.Luminosity into uEmissive so the
  vertex lighting saturates properly for bright keyframes. Retail's
  FUN_0059da60 non-luminous path writes rep.Luminosity into
  material.Emissive via the cache +0x3c slot; we were instead using
  it as a post-fragment multiply which could only dim, never brighten.
  Net effect: daytime clouds now render saturated white, dome dims
  correctly at night (rep.Luminosity=0.11 → Emissive=0.11), stars
  and moon unchanged.

* terrain.vert: MIN_FACTOR 0.08 -> 0.0 per retail FUN_00532440 decompile
  (DAT_00796344 ambient-floor = 0.0). Back-lit terrain now falls to
  pure ambient rather than getting an 8% sun floor.

New research / tooling (no runtime impact):

* docs/research/2026-04-24-lambert-brightness-split.md — retail's
  ambient-brightness formula pinned from PE .rdata read + live
  RetailTimeProbe capture: effAmbBright = AmbBright + |sunDir| * 0.2
  where scale constant 0x0079a1e8 = 0.2f exactly.

* docs/research/2026-04-23-lightning-real.md — research note on the
  dat-baked PhysicsScript-driven lightning path (Rainy DayGroup has
  explicit PES-triggered flash SkyObjects with 5ms time windows).

* Corrections stapled to sky-decompile-hunt-{B,C}.md: DAT_00842778 is
  DirColor, DAT_0084277c is AmbColor (the hunt docs had the swap
  backwards).

* tools/RetailTimeProbe/Program.cs: extended with pid=NNNN selector,
  sky global probe (DirColor/AmbColor/AmbBright/sunDir/cache.amb),
  and the 0x0079a1e8 scale-factor readout.

* tools/SkyObjectInspect/: throwaway dat-inspector built by the Opus
  deep-dive agent. Identified GfxObj 0x010015EF as the stars layer
  (A8R8G8B8 128x128 texture, 4% bright-pixel ratio).

* src/AcDream.App/Rendering/TextureCache.cs: per-texture alpha
  histogram dump under ACDREAM_DUMP_SKY=1 for diagnosing "are the
  clouds decoded with proper alpha" type questions.

README: rewrite to reflect current state (playable pre-alpha rendering
Dereth with animated characters, day-night cycle, weather, etc.)
instead of the stale "Phase 0 dat inventory only" description.

All 742 tests green.
This commit is contained in:
Erik 2026-04-24 20:34:36 +02:00
parent 889b235886
commit 1d54880213
12 changed files with 1217 additions and 43 deletions

169
README.md
View file

@ -1,22 +1,173 @@
# acdream
Experimental modern open-source Asheron's Call client in C# / .NET 10.
A modern open-source C# / .NET 10 Asheron's Call client.
**Status:** pre-alpha, not playable. Phase 0 only — dat file asset inventory.
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.
**Stack:** .NET 10, [Chorizite.DatReaderWriter](https://github.com/Chorizite/DatReaderWriter) for dat parsing. Silk.NET + Avalonia planned for rendering/UI (not yet wired up).
**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.
**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.
## Stack
## Layout
- **Language:** C# .NET 10
- **Graphics:** [Silk.NET](https://github.com/dotnet/Silk.NET) (OpenGL 4.3)
- **Audio:** OpenAL via Silk.NET
- **Dat parsing:** [Chorizite.DatReaderWriter](https://github.com/Chorizite/DatReaderWriter)
- **Networking:** Custom UDP + ISAAC cipher + game-message layer, wire-compatible
with ACEmulator server
- `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.
## What works
## Run
- 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.
## 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"):**
```powershell
$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"
```
Or set `ACDREAM_DAT_DIR` and run without args.
## 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.