# acdream — milestones (morale + scope layer)

**Status:** Living document. Created 2026-05-12.
**Sits above:** [`docs/plans/2026-04-11-roadmap.md`](2026-04-11-roadmap.md) (the strategic phase index).
**Currently working toward:** **M1.5 — Indoor world feels right.**

---

## Why this document exists

The roadmap is a phase index — week- to month-scale, ~50 phases by the time
v1.0 lands. Phases ship in vertical slices (architecture-first, horizontal
completion deferred), which is the right strategy for a solo open-source
project at this scale — but it leaves a chronic "everything is half-built"
feeling because no single phase ship feels like a real milestone.

This document sits **one altitude above** the roadmap. Each milestone is:

- **~6–10 weeks of focused work** (not a single phase, not a whole year).
- Defined by a **concrete playable scenario** — when the scenario works
  end-to-end, the milestone lands.
- A **scope-freeze event**: when a milestone lands, the phases it covers go
  off-limits until v1.0's final polish pass (M7).

Crossing a milestone is a textual event — milestones doc gets the writeup,
the freeze list flips, CLAUDE.md's "currently working toward" line advances.
Phases ship; milestones **land**.

---

## Operating rules

1. **One active milestone at a time.** Everything not on the critical path to
   the current milestone gets filed in `docs/ISSUES.md` with a `post-MN` tag
   and explicitly muted until the milestone hits. This is what kills the
   jumping-between-things feeling.

2. **Frozen phases are off-limits.** "Frozen" means no rework, no polish, no
   follow-up commits unless something is actively broken (player crash,
   regression). Visual nice-to-haves, "while I'm here" cleanups, and
   architecture second-guesses are all post-M7. The freeze is the discipline
   mechanism that makes the milestone meaningful — without it, M0's many
   shipped phases keep silently consuming attention.

3. **The milestone log is the morale instrument.** When a milestone hits:
   - Pin a one-paragraph writeup at the top of this doc describing what
     works end-to-end (any caveats or known regressions are explicit).
   - Update the freeze list. Update CLAUDE.md's "currently working toward"
     line to the next milestone.
   - NO demo videos. User explicitly removed that requirement 2026-05-16
     ("pointless of recording videos, for what purpose?").

4. **State both altitudes at session start.** "Currently working toward M1.
   Current phase: L.2 collision. Next concrete step: L.2d slice 1 spec." This
   keeps the high-level orientation visible alongside the immediate task and
   makes mid-session drift obvious.

---

## The milestones

### M0 — "Connect & explore" — ✅ DONE (crossed months ago)

**Demo scenario:** Log in, walk Holtburg in chase camera, see other characters
animate, send a chat message and see the echo, watch day turn to night,
listen to footsteps and ambient audio.

**Phases included (frozen):**

| Phase | What landed |
|---|---|
| 1–3 | Terrain + per-vertex normals + per-cell texture blending |
| 4 | UDP codec + handshake + character login + WorldSession |
| 5 | ObjDesc: AnimPartChange + TextureChanges + SubPalettes + ObjScale |
| 6.1–6.7 | Motion + animation foundation (idle, frame playback, slerp, UpdateMotion, UpdatePosition) |
| 7.1 | EnvCell room geometry — walls/floors/ceilings |
| 9.1–9.2 | Translucent render pass + back-face culling |
| A.1–A.5 | Streaming landblock loader → two-tier streaming |
| B.1–B.3 | Outbound ack pump + player movement + physics MVP resolver |
| D.1, D.2a | 2D overlay + ImGui scaffold + `AcDream.UI.Abstractions` layer |
| E.1–E.5 | Motion hooks + audio + particles + combat wire + spell wire |
| F.1, F.2 | GameEvent dispatcher + item model + Appraise |
| G.1, G.2 | Sky + day/night + weather + dynamic lighting |
| H.1, I.1–I.8 | Chat wire + UI consolidation + holtburger inbound parity + combat translator |
| K, L.0 | Input architecture + retail bindings + Settings panel |
| N.0–N.6.1 | WB rendering migration (modern path mandatory) |
| C.1, C.1.5a/b | Particle system + portal/EnvCell static-script wiring |
| R.1–R.3 | Retail research infrastructure (PDB extract + named decomp) |

**Status:** This is everything shipped through 2026-05-12. ~25 phase ships.
**Worth saying out loud: this is the hard half of the project.** The engine
runs, the world renders correctly, the network connects, the input is wired,
the data layers for combat/spells/items/audio/particles all exist. What's
missing is the gameplay loop on top.

---

### M1 — "Walkable + clickable world" — ✅ LANDED 2026-05-16

**Landing writeup (2026-05-16, after Phase B.6 ship `d640ed7`):**
All four M1 demo targets work end-to-end. You can log into `+Acdream`
at Holtburg, walk freely without getting stuck (L.2 collision is
retail-faithful through the doorway). Click any inn door at any
range, press R, the character runs (or walks if close) toward it
with the correct animation cycle including the leg-shuffle turn-first
phase, opens the door via ACE's server-side `MoveToChain` callback.
Same for clicking an NPC — runs over, body rotates to face, dialogue
fires from ACE without any client-side retry. Pickup items at any
range with F or R; spell components, food, money, weapons all work;
signs and other `BF_STUCK` scenery correctly block at the gate.
AP cadence matches retail (0 Hz idle, ~1 Hz smooth motion, per-event
on cell/plane changes). Run/walk threshold matches retail observation
(1m of distance left to walk). The earlier ladder of workarounds —
client-side retry, per-frame chatty AP, MoveToState suppression
grace period — all deleted via the Phase B.6 architectural refactor
(`d640ed7`). M1's demo path is now bit-for-bit retail-faithful end
to end.



**Demo scenario:** Walk through Holtburg without getting stuck on the inn
doorway. Open the inn door. Click an NPC and see selection feedback. Pick
up an item from the ground.

**Demo-target status (as of 2026-05-14):**

| # | Target | Status | Evidence |
|---|---|---|---|
| 1 | Walk through Holtburg without getting stuck | ✅ met | L.2a/d/g shipped 2026-05-12; Holtburg doorway verified |
| 2 | Open the inn door | ✅ met | B.4b (interaction) + B.4c (swing animation) shipped 2026-05-13 |
| 3 | Click an NPC and see selection feedback | ✅ met | B.4b chain + chat handlers; verified 2026-05-14 (Tirenia + Royal Guard double-click → NPC dialogue in chat panel) |
| 4 | Pick up an item from the ground | ✅ met (close-range path) | B.5 + post-B.5 `PickupEvent (0xF74A)` fix shipped 2026-05-14; visual-verified at Holtburg; creature-pickup guard added in `a01ebd5` |

**Landing artifacts done 2026-05-16:**
- ✅ Landing writeup pinned at top of this milestone block (above the table).
- ✅ Freeze list applied (see below).
- ✅ `CLAUDE.md`'s "currently working toward" advanced to M2.

**Known polish items deferred to post-M7 (do not gate M1 landing):**
- **#61** — AnimationSequencer link→cycle frame-0 flash on door swing. LOW.
- **#62** — PARTSDIAG null-guard. Latent, not reachable today.
- **#63** — ✅ CLOSED by Phase B.6 (`d640ed7`). Server-initiated
  `MoveToObject` is now honored end-to-end; ACE's `MoveToChain`
  callback fires server-side on arrival.
- **#64** — Local-player pickup animation does not render (retail
  observers see it correctly). LOW.
- **#69, #74, #75** — all closed by Phase B.6 (`d640ed7`). Turn-first
  animation, retail-narrow AP cadence, body-direct auto-walk
  architecture.

**Phases that shipped to clear M1:**
- **L.2 (a + d + g sub-lanes)** — Movement & Collision Conformance.
  L.2a slices 1+2+3 + L.2d slice 1+1.5 + L.2g slice 1+1b+1c shipped
  2026-05-12 / 2026-05-13. Visual-verified via the B.4b doorway test.
- **B.4b** — outbound Use + `WorldPicker` + double-click detection +
  `CollisionExemption` widening + `ServerGuid→entity.Id` translation
  (the ID-mismatch trap surfaced during L.2g slice 1c). Shipped
  2026-05-13.
- **B.4c** — door swing animation: spawn-time `AnimationSequencer`
  registration + stance-value fix (`NonCombat = 0x3D` not `0x01`, which
  had been causing doors to render halfway underground). Shipped
  2026-05-13.
- **B.5** — `BuildPickUp` (PutItemInContainer 0x0019) + `SendPickUp`
  helper + F-key wiring + new `PickupEvent (0xF74A)` despawn handler.
  Shipped 2026-05-14.
- **B.5 polish** (`a01ebd5`) — guard `SendPickUp` against creature
  targets so F-on-NPC produces a "Can't pick that up" toast instead of
  the malformed pickup that triggered ACE's `WeenieError 0x0029` + NPC
  emote chain. (Briefly visited adding "You pick up the X." chat /
  toast feedback for ground pickups in `87ba5c9`, then reverted in
  `20ecb23` — retail doesn't show that line for ground pickups; only
  for items received from NPCs / other characters, and that path is
  separate.)

**Freeze on landing:**
- L.2 zone (collision, cell ownership, transition parity, wire authority)
- B.4 zone (interaction outbound)
- B.5 zone (pickup outbound + inbound despawn)

**What "M1 lands" looks like:** the existing Holtburg traversal works as a
retail player would expect. Doorways are walkable. Buildings have solid
walls. Outdoor cell seams report the right cell. Clicking an NPC selects it
and produces NPC chat. The Use action opens doors. F picks up items at
close range and the player sees "You pick up the X." in chat.

---

### M1.5 — "Indoor world feels right" — 🔵 ACTIVE (resumed 2026-05-21 after Phase O ship)

**Phase O — DatPath Unification — shipped 2026-05-21.** ONE thing
touches the DATs. ~33 WB files (~7.7K LOC) extracted into
`src/AcDream.{Core,App}/Rendering/Wb/`; project references to
`WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend` dropped;
`DefaultDatReaderWriter` eliminated. Visual side-by-side passed at
Holtburg town, inn interior, and dungeon. Phase O pre-empted M1.5
per user direction 2026-05-21; M1.5 now resumes from the 2026-05-20
baseline with no code changes lost — Phase O did not touch dat-loading
infrastructure for physics or collision, only the rendering pipeline.
M1.5's planned phases (A6 + A7) are unaffected.

**Demo scenario (updated 2026-05-21):** The original demo target was
"enter the Holtburg Sewer dungeon" but that location doesn't exist on
this ACE server (discovered during A6.P1 capture session). Additionally,
A6.P1 surfaced **issue #95** (portal-graph visibility blowup) which makes
ANY dungeon visually unusable on entry. Demo scenario revised in two
parts:

**Building/cellar demo (achievable after A6.P3 lands):**
Walk into the Holtburg inn, climb to the 2nd floor, walk around without
sling-out or wall-clip. Enter a cottage cellar, descend without falling
through. Throughout:
- Walls block — no walk-through anywhere, indoor or stab-shell.
- Stairs work — ascend + descend without falling through or stuck-in-falling.
- Items block — furniture, decorations.
- Cell transitions are smooth — no CellId ping-pong, no flicker.
- Lighting reads correctly — torchlit rooms are bright, no spotlight
  artifacts, static decorations participate in env lighting.

**Dungeon demo (blocked on issue #95 fix; promote to post-M1.5 if
the visibility bug isn't addressed in M1.5 scope):**
Enter any dungeon via portal (substitute for "Holtburg Sewer"). Navigate
~3-5 rooms without rendering corruption (no see-through-walls, no
other-dungeons-rendered-inside). Walls block, stairs work, items block,
lighting correct, cell transitions smooth.

**Why this is its own milestone:** M1 landed walkable + clickable as a
specification (the doorways open, NPCs select, items pick up — all visible
in the demo target). But continued indoor testing surfaced a deep family of
physics + lighting bugs (BSP push-back distance probably diverges from
retail, per-frame ContactPlane synthesis is a known unfaithful stop-gap,
indoor lighting + item-spotlight bugs reported during 2026-05-21 sessions).
Three workarounds shipped today (#89 sphere-overlap CheckBuildingTransit,
#90 sphere-overlap stickiness, #92 spawn-cell-id seed) closed the visible
symptom at Holtburg inn, but #90 specifically is a CLAUDE.md-rules
workaround (explicit retail divergence) that needs a proper root-cause fix.
The umbrella indoor-physics issue (#83) has been open since 2026-05-19 with
multiple aborted fix attempts. Promoting this to milestone scope forces the
fix to be central, retail-anchored, and complete — not another whack-a-mole
patch.

**Phases included:**

| Phase | What it does |
|---|---|
| A6 — Indoor physics fidelity (cdb-driven) | Capture retail's per-tick BSP collision response state at 9 scenarios (4 buildings + 5 dungeon sites). Analyze the gap vs ours. Fix BSP correction paths. Remove #90 stickiness + `TryFindIndoorWalkablePlane` synthesis workarounds. |
| A7 — Indoor lighting fidelity (RenderDoc + retail-decomp) | Capture per-cell light state + per-pixel attribution at the same 9 scenarios. Analyze cell-light association, visibility culling, per-entity light direction. Fix indoor lighting + #80 (upper-floor dark) + #81 (static-stab atmospheric) + the held-item-spotlight bug. |

**Issues in scope (M1.5):**

- **#80** — Camera on 2nd floor goes very dark
- **#81** — Static building stabs don't react to atmospheric lighting
- **#83** — Indoor multi-Z walking broken (cellars, 2nd floors, intermittent falling-stuck)
- **#88** — Indoor static objects vibrate (suspected sub-step state corruption — A6.P2 maps to Finding 2 family)
- **#90** — CellId ping-pong (workaround in place; remove during A6.P4)
- **#95** — Portal-graph visibility blowup (filed 2026-05-21; **blocks the dungeon half of the M1.5 demo** but is NOT in A6 scope; either add a dedicated phase inside M1.5 to fix it OR promote the dungeon demo to post-M1.5)
- **L-indoor** — Lighting indoors broken (file as new # during M1.5 kickoff)
- **L-spotlight** — Items projecting spotlight on walls (file as new # during M1.5 kickoff)
- **Stairs walk-through** — captured + characterized by A6.P2 (Finding 2 family); fix in A6.P3
- **2nd-floor walking** — captured + characterized by A6.P2 (Finding 2 — scen3 shows infinite CP-write ratio on flat 2nd-floor walk); fix in A6.P3
- **Cellar descent** — same physics family as stairs; fix in A6.P3
- **Indoor sling-out** (new symptom from A6.P1 scen4) — captured + mapped to A6.P2 Finding 3 (cell-resolver in ResolveCellId / CheckBuildingTransit); fix in A6.P3
- **`TryFindIndoorWalkablePlane`** — synthesis workaround removal (Bug A's original goal, finally unblocked; A6.P4)

**Frozen phases during M1.5:** all M0 + M1 phases stay frozen. Plus
specifically the recently-shipped A4 + #89 + #91 + #92 (today's work) — those
land in main as the M1.5 baseline and shouldn't be revisited except as part
of A6.P4 removal of the workarounds.

**Estimated timeline:** 3–5 weeks calendar (17–26 days focused work). Bigger
than a normal milestone because lighting is open-ended (less existing
diagnostic infrastructure than physics). Could be shorter if the cdb
analysis surfaces a single-fix opportunity.

**What "M1.5 lands" looks like:** the indoor world reads as solid. Players
can navigate buildings, basements, multi-floor inns, and dungeons without
encountering walls they walk through, lighting that looks wrong, or
position glitches. The two known workarounds (#90 stickiness +
TryFindIndoorWalkablePlane synthesis) are removed; the codebase no longer
has indoor-physics "duct-tape." Dungeons are usable enough to support M2's
"kill a drudge" demo target (drudges live in dungeons; this milestone
unblocks that).

---

### M2 — "Kill a drudge" — ⏸ DEFERRED until M1.5 lands (was: NEXT)

**Demo scenario:** Equip a sword. Walk to a drudge. Swing. See "You hit
Drudge for 12 slashing damage (87%)" in chat. Watch the swing animation
play. Drudge dies, drops loot. Pick up the loot. Open the inventory panel
and see it.

**Phases to ship:**
- **F.2 (panels)** — Inventory panel reading `ItemRepository` (data already
  shipped in F.2 base; M2 ships the visual surface).
- **F.3** — Combat math + damage flow.
- **F.5a** — Visible-at-login dev panels (Attributes, Skills, Equipped,
  Inventory list) — minimal ImGui surfaces, retail-skin deferred to M5.
- **L.1c** — Combat animation wiring (draw/sheath, attack swings by
  stance/power/height, hit reactions, evades).
- **L.1b** — Command router + motion-state cleanup (prereq for L.1c).

**Freeze on landing:**
- Combat math zone
- Inventory zone (data + dev panel; retail-skin reopens in M5)
- L.1b/c combat-animation zone

**What "M2 lands" looks like:** the gameplay loop is real. You can fight,
take damage, kill things, see loot, manage your inventory. The game becomes
a game.

---

### M3 — "Cast a spell" — 🔵 (~3–4 weeks after M2)

**Demo scenario:** Cast Flame Bolt at a drudge. Watch the cast animation,
the projectile, the impact. Self-cast a buff (Strength Self). See the
enchantment in a buff list. Recall to lifestone — full recall animation,
correct teleport, correct re-spawn.

**Phases to ship:**
- **F.4** — Spell cast state machine (buffs + recalls first, projectile
  spells second).
- **L.1d** — Spell-casting animation wiring (cast command classification,
  windup, release, fizzle/interruption, recoil).
- **F.5 (Spellbook panel)** — dev-skin surface for learned spells + active
  enchantments.

**Freeze on landing:**
- F.4 spell zone
- L.1d cast-animation zone
- Spellbook dev-panel surface

**What "M3 lands" looks like:** mages are real characters. Buffs work,
recalls work, the first projectile spells work. Combat from M2 + casting
from M3 = retail-equivalent gameplay loop for melee and casters.

---

### M4 — "Live in the world" — 🔵 (~6–10 weeks)

**Demo scenario:** Create a fresh character from scratch (no ACE admin).
Spawn. Talk to an NPC. Accept a quest. Walk to a dungeon entrance. Portal
in (pink-bubble loading). Walk through the dungeon. Complete the quest.
Walk back out.

**Phases to ship:**
- **H.3** — Emote scripts + quests + dialogs (122 EmoteType × 39 Trigger
  mini-VM).
- **G.3** — Dungeon streaming + portal space + `PlayerTeleport` handling.
  (Unblocked by L.2e from M1.)
- **H.4** — Character creation (`0xE000002 CharGen` + heritages + appearance
  picker + preview).
- **L.1e** — Emote + posture animation wiring.

**Freeze on landing:**
- H.3 dialog/quest zone
- G.3 dungeon zone
- H.4 character-creation zone
- L.1e emote-animation zone

**What "M4 lands" looks like:** the world feels populated and interactive.
You can do quests, enter dungeons, create characters. Combined with M2/M3,
the client is **functionally playable** — minus visual polish.

---

### M5 — "Looks like retail" — 🟢 PARALLELIZABLE WITH M3/M4 (~4–8 weeks)

**Demo scenario:** Side-by-side screenshot of acdream vs retail at the same
location, same time of day, same character. Hard to tell apart at a glance.
Open the inventory panel — retail-skinned with the right font, icons, and
9-slice panel borders.

**Phases to ship:**
- **C.1.5c** — Sky-PES dispatch chain (closes #2 lightning, #28 aurora,
  #29 cloud thinness).
- **C.2** — Dynamic point lights (fireplaces, lamps, torches with proper
  local lighting).
- **C.3** — Palette range tuning (skin/hair/eye colors match retail).
- **C.4** — Double-sided translucent polys.
- **D.2b** — Custom retail-look UI backend.
- **D.3–D.7** — AcFont + dat sprites + core panels reskinned + HUD orbs +
  cursor manager.
- **L.1f** — NPC/monster + item-use animation coverage.

**Freeze on landing:**
- Visual polish zone (C.1.5c, C.2, C.3, C.4)
- D.2b → D.7 UI-skin zone
- L.1f NPC-anim zone

**What "M5 lands" looks like:** the client visually convinces. Screenshots
become postable. The "old / broken vs retail" feeling that drives most of
the chronic ISSUES.md entries is gone.

---

### M6 — "Plugins ship" — 🟢 (~4 weeks)

**Demo scenario:** A third party (not you) writes a small plugin against
the published API — XP tracker, loot logger, simple chat filter — and it
loads cleanly. Sample plugin lives in the repo with documented build steps.

**Phases to ship:**
- Plugin API surface: stable contract over `AcDream.Core` + `AcDream.UI.Abstractions`,
  versioned, with the world-state interfaces exposed.
- Plugin host: load isolation, lifecycle, error containment.
- Sample plugin (XP tracker or loot logger) — proves the API by using it.
- Plugin docs page.

**Freeze on landing:**
- Plugin API v1 surface (additive changes only post-freeze).

**What "M6 lands" looks like:** the differentiator vs the retail client is
real. acdream offers something retail never did, and the API is documented
well enough that other people can build on it.

---

### M7 — "v1.0" — 🟢 (open-ended polish)

**Demo scenario:** Long-running stress test: log in, play for 4 hours
across outdoor + dungeon + portal + combat + spell + chat scenarios,
reconnect once mid-session, log out clean. No crashes, no protocol errors,
no visual regressions, no audio dropouts.

**Phases to ship:**
- **Phase M** — Network stack conformance (retransmit, ACK piggybacking,
  echo/keepalive, fragment splitting, typed actions). Deferred until now
  because ACE handles loss gracefully — but v1.0 needs proper network
  hardening.
- **H.2** — Allegiance.
- **N.6 slice 2 + N.7–N.10** — Finish WB rendering migration (EnvCells,
  sky/particles via WB, visibility manager, GL infrastructure
  consolidation).
- **Phase J long-tail** — Player rig polish, group/fellowship UI, trade
  window, salvage/tinker UI, house ownership, society UI, dev-mode tools.
- **L.1g** — Animation polish + conformance.
- Final visual + audio polish pass against ISSUES.md chronic backlog.

**What "M7 lands" looks like:** v1.0. Ship.

---

## Estimated timeline

| Milestone | Effort | Cumulative |
|---|---|---|
| M0 | DONE | DONE |
| M1 | ~4–6 wk | ~5 wk |
| M2 | ~6–10 wk | ~13 wk |
| M3 | ~3–4 wk | ~17 wk |
| M4 | ~6–10 wk | ~25 wk |
| M5 | ~4–8 wk (parallel) | overlaps M3/M4 |
| M6 | ~4 wk | ~29 wk |
| M7 | open-ended | v1.0 |

**Roughly 9–12 months of focused solo work from 2026-05-12 to v1.0.** That's
honest for an open-source project of this scale. The biggest single rock is
M2 (combat math + animations + inventory panels lining up); M5 can be
chipped at in parallel by subagents while you drive M3/M4.

---

## What this document is **not**

- **Not a release schedule.** Internal morale + scope layer only. If acdream
  goes public-alpha at some point, that's a separate decision built on top
  of one of these milestones.
- **Not immutable.** When reality and the milestones diverge, update the
  milestones in the same session you discover the divergence. Same rule as
  the roadmap.
- **Not a replacement for the roadmap.** Phases are still where the
  implementation details live. This doc is the orientation layer above them.
- **Not granular enough for daily work.** Daily work happens at the phase /
  sub-phase / commit level. The milestone is the multi-week target you're
  aiming at.
