diff --git a/CLAUDE.md b/CLAUDE.md index ad590b94..cbd115a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -232,6 +232,20 @@ J-owner, both retail open paths, staged-item trading marker AD-93/AD-94 filed, AD-85 narrowed, AD-81 amended, AD-89/AD-95 retired. Filed: #393 (texture-detail options, post-M4). +**Campaign LA — the alpha launcher (ACTIVE 2026-08-14):** Avalonia +launcher/installer/updater (Windows+Linux) + the retail character- +management screen, driven autonomously under a user-set goal: Fable +plans, Sonnet implements, Opus dual-lens reviews (architectural + +retail-faithful). Spec: +`docs/superpowers/specs/2026-08-14-launcher-campaign-design.md`; plan + +ledger: `docs/plans/2026-08-14-launcher-campaign.md`; START at +`claude-memory/project_launcher_direction.md`. Key recon corrections +already binding: retail's select screen (`gmCharacterManagementUI`) has +NO 3D preview (chargen-only machinery); UI Studio no longer exists +(deleted at Campaign V — ignore stale memory/docs claims otherwise); +App `Program.cs` has no subcommand dispatch (the `--session-config` flag +is additive). + **Placement cutover — C4 COMPLETE 2026-08-05, merged to main.** Every placement route now runs through the canonical residence + continuation- executor owner. Routes landed this session: 4b-3 remote teleport/cell-less diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index c4fd0cf9..84534cdb 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -1,6 +1,6 @@ # acdream — strategic roadmap -**Status:** Living document. Updated 2026-08-03. **M3 landed; M4 is active.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. All eight slices of the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md), their automated closeout, and the user's connected visual matrix are complete. Modern Runtime J3 canonical entity/object lifetime and J4 gameplay-state ownership are closed at `89e6b207`; J5.1 canonical selection/combat/target-mode ownership is closed at `b298f99f`, J5.2 interaction transactions at `f5f7b417`, J5.3 combat/magic intent at `20df9d15`, J5.4 local movement/outbound cadence at `aa3f4a60`, J5.5 per-session physics/remote simulation at `7e6033d0`, J5.6 projectile simulation at `2aee3356`, and J5.7 combined simulation closeout at `cdee7a4b`. J6.1 world-environment ownership is closed at `902076c0`; J6.2 canonical reveal generation and typed destination readiness is closed at `a6860d55` plus `acb845d8`; J6.3 exact F751/Position destination correlation is closed at `6a063a27`; J6.4 exact graphical-host acknowledgement and owner cleanup is closed at `18d17d8b`. J7's one graphical `GameRuntime` root is closed at `ce41efb9`, including the user's 2026-07-27 exact post-cutover visual acceptance. J8 closed Slice J at `a9a822f2` with one shared graphical/no-window root and generation-reset transaction. Slice K Linux headless/multi-session work is closed. K0's tested no-presentation Windows/Linux boundary closed at `aada8a37`, K1's portable single-session host at `f8cb840f`, K2's deterministic scheduler and shared bot API at `7e8acb74` plus `38e83640`, and K3's shared-content/isolation plus connected observer gate at `3f340125`. K4 closed through `776482da`: 1/5/10/30-root isolation and two-hour simulated endurance, death/randomized cancellation, committed resource ceilings, ten minutes of exact native Linux two-account connected sampling, ACE-confirmed graceful logout, and zero-debt Runtime/content convergence all pass. Slice L Linux graphical/platform work is parked at its L1 implementation checkpoint by user direction on 2026-07-27. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate. +**Status:** Living document. Updated 2026-08-14. **M3 landed; M4 is active.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. All eight slices of the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md), their automated closeout, and the user's connected visual matrix are complete. Modern Runtime J3 canonical entity/object lifetime and J4 gameplay-state ownership are closed at `89e6b207`; J5.1 canonical selection/combat/target-mode ownership is closed at `b298f99f`, J5.2 interaction transactions at `f5f7b417`, J5.3 combat/magic intent at `20df9d15`, J5.4 local movement/outbound cadence at `aa3f4a60`, J5.5 per-session physics/remote simulation at `7e6033d0`, J5.6 projectile simulation at `2aee3356`, and J5.7 combined simulation closeout at `cdee7a4b`. J6.1 world-environment ownership is closed at `902076c0`; J6.2 canonical reveal generation and typed destination readiness is closed at `a6860d55` plus `acb845d8`; J6.3 exact F751/Position destination correlation is closed at `6a063a27`; J6.4 exact graphical-host acknowledgement and owner cleanup is closed at `18d17d8b`. J7's one graphical `GameRuntime` root is closed at `ce41efb9`, including the user's 2026-07-27 exact post-cutover visual acceptance. J8 closed Slice J at `a9a822f2` with one shared graphical/no-window root and generation-reset transaction. Slice K Linux headless/multi-session work is closed. K0's tested no-presentation Windows/Linux boundary closed at `aada8a37`, K1's portable single-session host at `f8cb840f`, K2's deterministic scheduler and shared bot API at `7e8acb74` plus `38e83640`, and K3's shared-content/isolation plus connected observer gate at `3f340125`. K4 closed through `776482da`: 1/5/10/30-root isolation and two-hour simulated endurance, death/randomized cancellation, committed resource ceilings, ten minutes of exact native Linux two-account connected sampling, ACE-confirmed graceful logout, and zero-debt Runtime/content convergence all pass. Slice L Linux graphical/platform work is parked at its L1 implementation checkpoint by user direction on 2026-07-27. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate. **Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file. **Slice L checkpoint:** L0 closed at `66f114b2` with one typed graphical @@ -86,6 +86,24 @@ full Release suite 12,221 passed / 4 skipped / 0 failed. Plan and ledger: in-client acceptance script: [`2026-08-09-campaign-ch-test-script.md`](../research/2026-08-09-campaign-ch-test-script.md). +**Campaign LA — launcher/installer/updater + retail character-select +(ACTIVE 2026-08-14):** the alpha-program launcher: an Avalonia app +(Windows + Linux) doing triple duty — install (DAT locate → `acdream-bake` +with progress → SHA record), update (GitHub Releases manifest, verified +download, atomic version swap, launcher self-update), and launch +(ThwargLauncher-model server × account × character profiles with full +in-UI CRUD; plaintext credential file by explicit user decision). +File-contract orchestration of both hosts: session config in (K1 shape + +plugins + login commands), password via child stdin, versioned JSONL +status events out. Adds the headless character-list probe, plugin hosting ++ login commands on both hosts, and the retail character-management +screen (recon-corrected: `gmCharacterManagementUI` is a flat listbox with +Enter/Delete/Restore — NO 3D preview on retail's select screen; Create is +a future campaign). Spec: +[`2026-08-14-launcher-campaign-design.md`](../superpowers/specs/2026-08-14-launcher-campaign-design.md); +plan + ledger: +[`2026-08-14-launcher-campaign.md`](2026-08-14-launcher-campaign.md). + **Remaining physics-divergence closeout (ACTIVE, checkpoint 2026-08-03):** the user then authorized retirement of the remaining proven collision/placement gaps before vendor work resumes. Nested retry, edge/StepDown/Path-6 ordering, exact cell diff --git a/docs/plans/2026-08-14-launcher-campaign.md b/docs/plans/2026-08-14-launcher-campaign.md new file mode 100644 index 00000000..1a104e65 --- /dev/null +++ b/docs/plans/2026-08-14-launcher-campaign.md @@ -0,0 +1,388 @@ +# Campaign LA — launcher / installer / updater + retail character-select + +**Status:** ACTIVE (started 2026-08-14) +**Spec (approved):** `docs/superpowers/specs/2026-08-14-launcher-campaign-design.md` +**Memory crib:** `claude-memory/project_launcher_direction.md` +**Branch:** `claude/acdream-launcher-credentials-4d2f7c` (merge to main at coherent checkpoints) + +Campaign LA ships the alpha launcher (Avalonia, Windows + Linux): triple-duty +launcher + installer + updater, ThwargLauncher-model profiles with full in-UI +CRUD, plaintext credential file (user-decided), file-contract orchestration of +`AcDream.App` and `AcDream.Headless`, plugins + login commands on both hosts, +the headless character probe, and the retail character-select screen (no +Create). All architectural decisions live in the spec — this plan sequences +the work. + +## Process (binding) + +- **Fable plans/sequences/integrates. Sonnet implements bounded slices. Opus + reviews at every slice boundary, dual-lens:** (a) architectural — ownership, + layering, dependency-guard integrity, seams; (b) retail fidelity vs + `docs/research/named-retail/` wherever the slice touches retail behavior. + Findings → fixes → narrow re-review. +- Max 3–4 agents in parallel including children; subagents never spawn + subagents; implementer prompts carry spec+plan paths, files-to-read, + acceptance criteria, commit style. +- `dotnet build` + `dotnet test` green before a slice is DONE; ≥1 commit per + slice tagged `Campaign LA`; retail deviations add their + `docs/architecture/retail-divergence-register.md` row in the same commit; + no workarounds without explicit user approval. +- Connected/visual gates are the ONLY stop-and-wait points; each gets an + exact script under `docs/research/` and non-blocked slices keep moving. + +## Slice map + +| Slice | Deliverable | Depends on | +|---|---|---| +| LA0 | `AcDream.Platform` extraction (`ApplicationPathSet`) + guard amendments | — | +| LA1 | Launch contract: App `--session-config` + stdin credential; status.jsonl writer both hosts; roster plumbing | LA0 | +| LA2 | Headless probe mode + `idle` policy | LA1 | +| LA3 | `AcDream.Launcher.Core`: profile store CRUD, config composition, spawn/supervise, status reader | LA0 (LA1 contract shapes) | +| LA4 | `AcDream.Launcher` Avalonia UI: CRUD views, per-char settings, sessions, probe action | LA3 | +| LA5 | Plugin hosting: headless `IPluginHost` + capability flag; session-driven plugin set both hosts | LA1 | +| LA6 | Login commands: parser-core extraction + execution on both hosts | LA1, LA5 | +| LA7 | Character-select: Runtime selection state + wire (delete/restore/error) + no-selector flow | LA1 | +| LA8 | Character-select authored retail screen (flat listbox — NO 3D preview, recon-corrected) | LA7 | +| LA9 | Installer: first-run wizard (DAT locate/validate, bake w/ progress, SHA record) | LA3, LA4 | +| LA10 | Updater: GitHub Releases manifest, download/verify/install/swap, self-update | LA3, LA4 | +| LA11 | Closeout: connected-gate script, roadmap/CLAUDE.md/memory, program ledger | all | + +Parallelism guide: LA3/LA4 (launcher side) proceed alongside LA5–LA8 (client +side) — different assemblies, no shared files. LA9/LA10 close the launcher +side; LA11 closes the campaign. + +## LA0 — `AcDream.Platform` extraction + +New BCL-only project `src/AcDream.Platform/` holding `ApplicationPathSet` + +`IApplicationPathEnvironment` (today +`src/AcDream.Runtime/Platform/ApplicationPathSet.cs` — self-contained, no +intra-Runtime dependencies; clean cut). Runtime/App/Headless reference it. + +Recon facts (2026-08-14): blast radius is the definition, six source files +(`GraphicalHostPlatformServices.cs`, `GraphicalLegacyConfigurationMigrator.cs`, +`App/Program.cs`, `GameWindow.cs:533`, `HeadlessPathSet.cs`, +`HeadlessPlatformEnvironment.cs`; two more files are doc-comment-only), two +test files (`ApplicationPathSetTests.cs` moves to a new +`tests/AcDream.Platform.Tests/` or stays keyed to the new assembly; +`GraphicalLegacyConfigurationMigratorTests.cs` fixtures), and ONE dependency +guard: `tests/AcDream.Headless.Tests/HeadlessDependencyBoundaryTests.cs` +`HeadlessAssemblyReferencesOnlyTheRuntimeProject` asserts Headless references +exactly `[AcDream.Runtime.csproj]` — amend to the exact new set in the same +commit (deliberate, never silent). Namespace stays `AcDream.Runtime.Platform`? +NO — rename to `AcDream.Platform` and fix the eight usings (clean naming beats +avoiding a mechanical edit). Register new projects in `AcDream.slnx`. + +**Acceptance:** build + full test suite green; guard test asserts the new +exact reference set; launcher-side consumability proven by the LA3 project +referencing only `AcDream.Platform`. + +## LA1 — launch contract (client side) + +Three pieces, one slice, because they share the session-config/status seam: + +1. **App `--session-config `:** parsed once in `Program.cs` into + `RuntimeOptions` (code-structure rule 4); carries endpoint, account, + optional character selector, `Plugins`, `LoginCommands`, `Content` + (DatDirectory/PreparedAssetPath), status-file path, credential reference. + Recon: `Program.cs` has NO subcommand dispatch today — args handling is + one positional DAT-dir (`Program.cs:35`), so the flag is purely additive + (preserve the positional arg). The live-credential seam is a single call + site (`SessionPlayerComposition.cs:1128-1135` → + `LiveSessionConnectOptions`); the config path populates the same + `RuntimeOptions` fields from a different source. Env-var dev flow + untouched. App gains the `StandardInput` credential read (mirroring + `HeadlessCredentialResolver.ResolveStandardInput` — one line, immediately + wrapped in an erasable secret, redacted `ToString`; today + `RuntimeOptions.LivePass` is a bare string — the config path must not + widen that exposure). +2. **Status stream both hosts:** per-session `status.jsonl` (path given in + config; absent → no writer constructed, zero cost). Versioned event + vocabulary (`"v":1`): `started`, `connected`, `characterList`, + `enteredWorld`, `pluginLoaded`/`pluginFailed`, `disconnected`, `exited`. + Recon: today's `HeadlessDiagnosticWriter` is a single shared-stdout JSONL + sink with four kinds (lifecycle/failure/event/resources) and NO per-session + file — the status writer is a second, separate sink, not a rework of the + diagnostics writer. App has no structured writer today; it gets the same + shared implementation (lands in Runtime so both hosts borrow it). +3. **Roster plumbing:** `CharacterList.Parsed` is consumed inside + `LiveSessionController.StartCore` (`LiveSessionController.cs:612`) and + never escapes — add a typed roster report on the lifecycle-host seam + (`ILiveSessionLifecycleHost`) so hosts can emit the `characterList` status + event and (later) the char-select screen can populate. No behavior change + to selection itself in this slice. + +**Acceptance:** round-trip tests (config → `RuntimeOptions`; stdin credential; +status events in order with exact shapes; roster surfaced); App/Headless/ +Runtime suites green; redaction test proves the password never appears in +status/diagnostics output. + +## LA2 — headless probe mode + `idle` policy + +Recon facts: the probe's shape already exists as the `NoCharacters` early-exit +(`LiveSessionController.cs:613-622` → `StopCore()` → 4-stage +`SessionScope.DrainTeardown`, graceful, `_inWorld == false` so no pre-logoff +flush) — but it fires only on selection FAILURE and maps to exit code 5 +(`HeadlessProcessHost.RunOnUpdateThread:203-212` treats any non-`Connected` +start as `ConnectionError`). + +1. **Probe:** a `Probe` flag on the connect options short-circuits `StartCore` + right after `GetCharacters` (before `TrySelectCharacter`): report roster, + `StopCore()`, return a NEW `LiveSessionStartStatus.ProbeComplete`. + `HeadlessProcessHost` maps it to exit code 0 with a final `characterList` + + `exited(reason: "probe")` status pair. Config: `mode: "probe"` on the + session descriptor relaxes the `JsonRequired` character selector + policy + for probe sessions ONLY (loader keeps strict validation otherwise — + recon: violations currently surface as raw `JsonException` → exit 3; probe + relaxation must be shape-level in the loader, not attribute removal). +2. **`idle` policy:** new consumer `HeadlessBotPolicy` id — enter world, run + plugins/login-commands (arrive in LA5/LA6), stay until stopped, clean + SIGINT teardown (K4's graceful-logout path already proves the mechanism). + +**Acceptance:** probe test (fixture session → roster event → graceful teardown +receipt → exit 0, no `EnterWorld` on the wire); loader tests for probe-shape +relaxation + strict normal validation; idle-policy lifecycle test; suites +green. Connected verification (user gate, LA11 batch): live probe against ACE +twice in a row with no lingering session (spec §11.9). + +## LA3 — `AcDream.Launcher.Core` + +New BCL-only project + `tests/AcDream.Launcher.Core.Tests/`. References +`AcDream.Platform` ONLY. + +- Profile store: `launcher-profiles.json` (spec §5 schema) — load/save/ + validate, full CRUD operations, roster merge (fold `characterList` events + in, preserving per-character user settings), 0600 on Linux. +- Session-config composition: profile + install records → the LA1 config + shape (typed writer; probe shape included). Passwords excluded — stdin only. +- Process orchestration: spawn App/Headless per launch mode, feed password to + child stdin then close, supervise lifetime, tail `status.jsonl` + (share-tolerant reads), surface typed session state. +- SHA-256 utility (pak record + download verify — consumed by LA9/LA10). + +**Acceptance:** CRUD/round-trip/merge tests; composition tests (all three +modes + probe); supervision tests against a fake child process (echo script); +status-tail tests including partial-line handling; suites green. + +## LA4 — `AcDream.Launcher` (Avalonia) + +New Avalonia project (Windows + Linux). MVVM over Launcher.Core; no game +solution references beyond `AcDream.Platform` transitively. + +- Views: server list → accounts → characters tree; add/edit/remove dialogs + for servers (name/host/port) and accounts (account + password entry); + per-character settings editor (launch mode, plugin set, login commands); + per-account "refresh characters" (probe); running-sessions status column. +- Launch actions per mode (`gui` / `guiSelect` / `headless`); probe disabled + while the launcher runs a session for that account. +- First-run wizard shell + update prompt shell (bodies land in LA9/LA10). + +**Acceptance:** ViewModel tests in Launcher.Core.Tests patterns (VMs live in +the Avalonia project but stay logic-thin; anything testable pushes down); +build green on Windows; `linux-x64` publish compiles. Visual polish is gated +at LA11 (user). + +## LA5 — plugin hosting on both hosts + +Recon facts (2026-08-14): `PluginLoader`/`PluginDiscovery`/`PluginManifest` +already live in `AcDream.Core` (Headless-reachable). App's single load loop +(`App/Program.cs:110-121`) loads ALL discovered plugins from two roots +(`AppContext.BaseDirectory/plugins` + `ApplicationPathSet.PluginsDirectory`, +dup-id skip) — no allow-list exists on either host. `AppPluginHost` is a +26-line pass-through; three of four `IPluginHost` surfaces (`State` → +`WorldGameState`, `Events` → `WorldEvents`, `Selection` → `SelectionState`) +are backed by Core-owned types already; only `Ui` (`BufferedUiRegistry`) is +genuinely App-only. Headless has zero plugin hosting today (confirmed). + +1. Session-config `Plugins` allow-list filters the discovery result on BOTH + hosts (absent list = load all, preserving today's dev behavior). +2. `HeadlessPluginHost : IPluginHost` in Headless over the same Core-owned + `State`/`Events`/`Selection`; `Ui` is an explicit no-op behind a new + capability flag on `IPluginHost` (e.g. `HasUi`) so plugins can detect + headless. Contract documented in `Plugin.Abstractions`. +3. `pluginLoaded`/`pluginFailed` status events from both hosts' load loops. + +**Acceptance:** fixture plugin in Headless suite (load, capability flag, +markup no-op, teardown via collectible ALC); allow-list filter tests both +hosts; status events asserted; suites green. + +## LA6 — login commands on both hosts + +Recon facts (2026-08-14): the command core is dependency-CLEAN — +`ChatInputParser` (zero usings), `ChatCommandRouter` (BCL + +`AcDream.Core.Chat`), `RetailClientCommandCatalog` (FrozenDictionary), +`ChatVM` (Core.Chat/Combat + `System.Numerics` only), `ICommandBus` + the +four command records (BCL-only). The block is assembly identity, not +coupling. `ChatCommandRouter.Submit`'s two entanglements: a hard `ChatVM` +parameter (uses only `ShowInterfaceText`/`ShowSystemMessage`/ +`LastIncomingTellSender`/`LastOutgoingTellTarget`) and the `ICommandBus`, +whose production implementation (`LiveSessionCommandRouter`, +`App/Net/LiveSessionCommandRouter.cs`) is App-only and wraps wire-send +delegates from the live session. GUI already has a login-command analog: +`RetailUiAutomationScriptRunner` feeds `ChatCommandRouter.Submit` at +`RetailUiRuntime.cs:523-527`. + +1. **Extraction:** move parser/router/catalog + `ICommandBus` + the four + command records (+ sibling tables they require) into Runtime + (`AcDream.Runtime/Chat/...`); the router's `ChatVM` parameter becomes a + narrow feedback interface defined beside it (exactly the four members + used); `ChatVM` (stays in UI.Abstractions) implements it. GUI path stays + bit-identical — same call sites (`ChatWindowController.cs:326`, + `FloatingChatWindowController.cs:157`), same routing, CH-accepted + behavior regression-checked by the existing chat suites. +2. **Headless dispatch:** a Runtime/Headless `ICommandBus` binding the same + session send delegates (`SendTalk`/`SendTell`/`SendChannel`/ + `SendTurbineChat`) + Runtime state that App's router binds — paralleling + `LiveSessionCommandRouter`'s registrations, feedback lands in + `RuntimeCommunicationState.AddText`. +3. **Execution:** both hosts run `LoginCommands` sequentially as-if-typed + (default 500 ms inter-command delay, config-overridable) once + entered-world; per-command failures → status stream, never abort. +4. K0 guard: if the code folds into Runtime, the single-reference assertion + stands untouched; the forbidden-prefix closure tests keep passing. Any + guard text change is deliberate and documented. + +**Acceptance:** extraction lands with zero GUI chat test regressions +(UI.Abstractions + App chat suites bit-green); headless executes a +login-command script against a fixture session with ordered wire sends; +delay + failure-tolerance tests; suites green. + +## LA7 — character-select: state + wire + +Recon facts (2026-08-14): retail's screen is `gmCharacterManagementUI` +(`acclient.h:56545`) — flat listbox + Create/Enter/Delete/Restore buttons + +dialog contexts. **No 3D preview exists on retail's select screen** (the +`gmCG3DView`/`CreatureMode` viewport is chargen-only; the old +"rotating pedestal" line in `retail-ui/05-panels.md` §13 is uncited and +wrong). Our `CharacterList` parse already matches ACE's serializer exactly +(two-array shape, status/deleted always zero from ACE) and the two-phase +enter-world (0xF7C8 → 0xF7DF → 0xF657) is implemented. Missing wire: +delete/restore/error. + +1. **Wire messages** (`AcDream.Core.Net/Messages/`, retail citations in + file docs per house style): `CharacterDelete` 0xF655 — outbound + account String16L + **slot index** (`Proto_UI::SendDeleteCharacter + @0x00546b30`; NOT guid), inbound opcode-only ack followed by a fresh + CharacterList; `CharacterRestore` 0xF7D9 guid-only (ACE + holtburger + consensus; the decomp's apparent extra strings are a decompiler + artifact — spec §11.4), response 0xF643 (flag + guid + name + + secondsDisabled); `CharacterError` 0xF659 parser (new — today NO + character-stage server error can be surfaced). +2. **Runtime selection state** (J-owner pattern): roster with per-entry + greyed/pending-delete state (`SecondsGreyedOut != 0` ⇒ pending; ACE + sends a constant 1 during the grace window — treat as boolean, never a + countdown), highlight, pending-delete dialog state, typed commands + (highlight / enter / delete-request / delete-confirm / restore). + Retail behavior oracles: `RebuildCharacterList@0x004ec3a0`, + `SelectCharacter@0x004ec160`, `UpdateButtons@0x004ec240` + (Delete↔Restore swap on greyed state), `EnterGame@0x004ed440`. +3. **No-selector flow:** a graphical session config without a character + selector stops at selection state instead of auto-enter; the + first-available fallback (`CharacterList.TrySelectFirstAvailable`, + used at `LiveSessionController.cs:848-851`) remains ONLY for + selector-carrying/headless sessions. Selection feeds the existing + `EnterWorld` path unchanged. + +**Acceptance:** message round-trip tests against ACE's serializer shapes; +selection-state tests (greyed transitions, delete→list-refresh, restore, +error surfacing); no-selector stop + enter flow tests; suites green. + +## LA8 — character-select: authored retail screen + +Scope: project LA7's state through the REAL retail screen. No 3D preview +(recon-corrected; a preview would be an unapproved divergence). + +1. **Layout resolution:** retail resolves the root via + `UIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039a)` + + `DBObj::GetDIDByEnum(..., 5)` — reuse OP8's ported GetDIDByEnum + machinery (category 4 precedent) for enum-table 5; slice starts by + dumping that table from installed DATs to pin the concrete DataID. + Child ids: listbox `0x1000039d`, create `0x100003a0` (present, + disabled — Create is a future campaign), enter `0x100003a2`, delete + `0x1000039f`, restore `0x1000039e`. +2. **Dialogs:** delete-confirm, please-wait, entering-world, error — the + retail dialog machinery from the OP8 WaitDialog work (`2a81e813` + mapped WaitDialog class type 0x19) is the base. +3. **Open item resolved here:** whether retail draws a render-loop + background scene behind the UI (pseudo-C proves only that the UI class + owns no viewport) — settle via user recollection + the visual gate + before polishing. + +**Acceptance:** authored screen builds from DAT assets; button-state +matrix matches `UpdateButtons` oracle (incl. Delete↔Restore swap); +enter/delete/restore/error flows drive LA7 state end-to-end; suites +green. User visual gate at LA11 (screen look, dialog flows, delete + +restore against local ACE). + +## LA9 — installer (first-run) + +- DAT locate: auto-detect `%USERPROFILE%\Documents\Asheron's Call` and + `C:\Turbine\Asheron's Call` + manual picker; validate the four DATs. +- Bake: spawn `acdream-bake --dat-dir --out /pak/acdream.pak + --threads N`. Recon: default `--out` is INSIDE the DAT dir — the launcher + always passes `--out` explicitly. Progress: add `--progress-json` to + `AcDream.Bake` (JSONL progress lines alongside the existing 5-second human + text, which stays default) — scraping human text is fragile and we own the + tool. Recon: the bake has NO whole-file SHA — after a successful bake the + LAUNCHER computes and records SHA-256 + size + `BakeToolVersion` in its + install record, and re-verifies on subsequent startups (fast corruption + check trades a few seconds of hashing for never launching against a + half-written pak). +- Install record feeds LA3's session-config composition + (DatDirectory/PreparedAssetPath). + +**Acceptance:** wizard flow tests over Launcher.Core (fake bake child emitting +`--progress-json` lines); bake-tool progress flag tests in +`tests/AcDream.Bake.Tests`; SHA record/verify tests; suites green. Connected +gate (user): clean-profile first-run against real DATs. + +## LA10 — updater + +- Manifest: GitHub Releases; `manifest.json` release asset — version, per-RID + client zip URL + SHA-256 + size, minimum-launcher version. Launcher pins + owner/repo in its config. +- Client update: poll on launch (+ manual check), download to staging, SHA + verify, unpack to `DataDirectory/app//`, atomic `current.json` + pointer swap, refuse while any session runs, keep previous version for + one-step rollback. +- Launcher self-update: staged download + rename-dance on next start. +- Session-config composition targets `app/current`'s binaries. + +**Acceptance:** manifest/download/verify/swap tests against a local HTTP +fixture; rollback test; refusal-while-running test; self-update staging test; +suites green. Connected gate (user): staged-manifest update swap end-to-end. + +## LA11 — closeout + +- One connected-gate script `docs/research/2026-XX-XX-campaign-la-test-script.md` + covering: all three launch modes vs local ACE, probe round-trip ×2 (no + lingering session), char-select visual matrix + delete flow, login-commands + + plugin behavior on both hosts, add-server/add-account purely in UI, + clean-profile first-run wizard, staged update swap. +- Roadmap shipped-table entry, CLAUDE.md Current-state flip, memory distill, + ledger below completed, program closeout section. + +## Review protocol + +Per slice: implementer commit(s) → Opus dual-lens review (architectural + +retail-where-applicable) → fix round → narrow re-review of fixes → slice DONE +in ledger. Reviews name blast radius explicitly +(`claude-memory/feedback_blast_radius_single_lens.md`). Slices LA7/LA8 add the +retail-fidelity lens against named-retail symbols cited in the slice body; +LA6 adds CH-regression scrutiny; LA0 adds guard-integrity scrutiny. + +## Ledger + +| Slice | Status | Commits | Review | Notes | +|---|---|---|---|---| +| LA0 | — | | | | +| LA1 | — | | | | +| LA2 | — | | | | +| LA3 | — | | | | +| LA4 | — | | | | +| LA5 | — | | | | +| LA6 | — | | | | +| LA7 | — | | | | +| LA8 | — | | | | +| LA9 | — | | | | +| LA10 | — | | | | +| LA11 | — | | | | diff --git a/docs/superpowers/specs/2026-08-14-launcher-campaign-design.md b/docs/superpowers/specs/2026-08-14-launcher-campaign-design.md index 1003a54a..162778ff 100644 --- a/docs/superpowers/specs/2026-08-14-launcher-campaign-design.md +++ b/docs/superpowers/specs/2026-08-14-launcher-campaign-design.md @@ -40,7 +40,7 @@ Decided this session (2026-08-14): | Question | Decision | |---|---| | Headless launch purpose | **Run plugins** (VirindiTank-style) + login commands; same on GUI. Launcher selects which plugins per character | -| Character-select screen | **Retail screen, no Create.** 3D rotating preview + Enter World + Delete; Create Character deferred to its own campaign | +| Character-select screen | **Retail screen, no Create.** CORRECTED by 2026-08-14 recon: retail's `gmCharacterManagementUI` is a flat listbox + Enter Game / Delete / Restore buttons + dialogs — **no 3D preview exists on retail's select screen** (that machinery, `gmCG3DView`, is character-CREATION-only). We port what retail actually had; Create deferred to its own campaign | | Campaign scope | **Everything now** — launch flows + first-run install + update pipeline in one campaign | | Launcher ↔ client coupling | **Approach A: file-contract orchestrator** (config in, status events out; no game-protocol code in the launcher) | | Update feed | **GitHub Releases** (manifest.json + per-RID zips as release assets) | @@ -211,23 +211,50 @@ enters immediately. New behavior: **no character selector in options → stop at the retail character-select screen.** Selection there feeds the same `EnterWorld` path. -- **Ownership:** J-owner pattern. A Runtime-owned selection state (roster, - highlighted entry, pending-delete confirmation) with typed commands - (highlight / enter / delete-request / delete-confirm); App projects the - authored screen. Headless never uses it (config always carries a - selector; the loader already requires one). -- **UI:** imported retail screen via `LayoutImporter` (OP3/FA recipe). - The concrete LayoutDesc id and widget tree come from the - grep-named-first workflow + `docs/research/retail-ui/` during the plan; - retail decomp is the behavior oracle for list interaction, Enter World, - and Delete (including retail's delete confirmation flow — ACE serves - the character-delete message). -- **3D preview:** a void-scene render path — the selected character's - Setup + ObjDesc appearance rendered with a dedicated camera/lighting, - reusing the existing creature-appearance pipeline outside a landblock. - Own slice; the retail screen's rotation behavior is the oracle. -- **Non-goals:** Create Character (own future campaign); a login screen - ("back" exits the client — credentials always arrive via config/env). +**CORRECTED 2026-08-14 (named-retail recon):** retail's screen is +`gmCharacterManagementUI` (`acclient.h:56545`) — a `UIElement_ListBox` +character list plus Create / Enter Game / Delete / Restore buttons and +dialog contexts (delete-confirm, please-wait, entering-world, error). +**It has NO 3D preview** — the rotating-model viewport (`gmCG3DView` / +`UIElement_Viewport` / `CreatureMode`) exists only on character +CREATION's appearance/heritage/profession pages. The earlier +"3D preview on a pedestal" belief traced to one uncited line in +`docs/research/retail-ui/05-panels.md` §13. We port the real screen; a +preview would be a deliberate divergence we are NOT taking. + +- **Ownership:** J-owner pattern. A Runtime-owned selection state (roster + incl. greyed/pending-delete seconds, highlighted entry, pending-delete + confirmation) with typed commands (highlight / enter / delete-request / + delete-confirm / restore); App projects the authored screen. Headless + never uses it (config always carries a selector; the loader already + requires one). +- **UI:** imported retail screen. The root layout id is resolved + indirectly — retail calls + `UIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039a)` and + resolves the concrete DataID via `DBObj::GetDIDByEnum(..., 5)` (the same + GetDIDByEnum machinery OP8 already ported for key names, category 4). + Child widget ids from the decomp: listbox `0x1000039d`, create + `0x100003a0` (hidden/no-op this campaign), enter `0x100003a2`, delete + `0x1000039f`, restore `0x1000039e`. Behavior oracles: + `RebuildCharacterList@0x004ec3a0`, `SelectCharacter@0x004ec160`, + `UpdateButtons@0x004ec240` (Delete↔Restore visibility swap on greyed + state), `EnterGame@0x004ed440`, + `MakeDeleteCharacterConfirmationDialog@0x004ecca0`. +- **Wire (new messages):** `CharacterDelete` 0xF655 (outbound: account + String16L + **slot index**, per `Proto_UI::SendDeleteCharacter@0x00546b30` + — NOT the guid; inbound: opcode-only ack, then a fresh CharacterList), + `CharacterRestore` 0xF7D9 (guid) with response 0xF643, and a + `CharacterError` 0xF659 parser (currently absent — acdream cannot + surface any character-stage server error today). Enter-world's + two-phase handshake (0xF7C8 → 0xF7DF → 0xF657) is already implemented. +- **Open items** carried to the plan: the concrete layout DataID (dump + enum-table 5 from installed DATs), and whether retail rendered any + render-loop-level background scene behind the UI (the pseudo-C only + proves the UI class owns no viewport) — both resolved in the screen + slice before the user visual gate. +- **Non-goals:** Create Character (own future campaign; the Create button + exists on the authored screen but is disabled); a login screen ("back" + exits the client — credentials always arrive via config/env). - Retail-workflow rules apply: any behavioral deviation ships with its divergence-register row in the same commit. @@ -284,7 +311,8 @@ same `EnterWorld` path. → `RuntimeOptions`, stdin credential resolution, status-event writer output shape, plugin-set narrowing, login-command execution order. - **Character-select:** Runtime selection-state tests; authored screen - via UI Studio dumps/screenshots; behavior against retail oracle. + exercised by focused App tests (UI Studio no longer exists — deleted at + Campaign V); visuals settle at the user gate. - **Headless plugin host:** fixture plugin in the Headless suite (load, capability flag, teardown). - **Connected gates (user-driven):** every launch mode against local ACE @@ -301,12 +329,22 @@ same `EnterWorld` path. after reading CH seams; do not regress CH-accepted chat behavior. 2. Current App plugin-loading behavior (what loads today, when) — read before wiring the session-driven set. -3. Retail character-select LayoutDesc id + widget tree — research task - (grep-named + `docs/research/retail-ui/`). -4. Character-delete wire message + ACE handling — verify against - holtburger/ACE before implementing the Delete flow. -5. Void-scene preview: lighting/camera parameters need the retail oracle - (what does retail actually render behind the character?). +3. RESOLVED by recon except the concrete root-layout DataID: widget tree + + behavior symbols are in §7; the DataID hides behind `GetDIDByEnum` + enum-table 5 (enum `0x10000005`) — dump the table from installed DATs + in the screen slice. +4. RESOLVED by recon: delete = 0xF655 account+slot (ack opcode-only, then + fresh CharacterList); restore = 0xF7D9 guid → 0xF643 response; ACE's + `secondsSincePendingDelete` computes to a constant 1 during the grace + window (ACE quirk — treat any non-zero as "pending delete", don't + display it as a countdown). One decomp ambiguity (whether retail's + restore carries two extra strings) is noted as a likely decompiler + artifact — ACE + holtburger agree on guid-only; follow them and note + the register row is NOT needed (wire consensus, no behavior change). +5. Whether retail rendered a render-loop-level background scene behind the + character-management UI — pseudo-C only proves the UI class owns no + viewport. Resolve in the screen slice (user memory of retail + the + visual gate settle it). 6. Bake tool 4 invocation surface from Launcher.Core (in-process reference vs child process) — child process preferred to keep Launcher.Core free of game-solution references; confirm the tool's CLI is sufficient.