docs: Campaign LA design spec — launcher/installer/updater + char select
Approved brainstorm outcome for the alpha launcher campaign: Approach A file-contract orchestrator (session config in, stdin credential, JSONL status events out), full in-UI CRUD for servers/accounts/credentials, headless character-list probe, retail character-select screen (no Create), plugins + login commands on both hosts, first-run DAT locate/bake install, GitHub Releases update feed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cca8f20738
commit
afb4e82943
1 changed files with 326 additions and 0 deletions
326
docs/superpowers/specs/2026-08-14-launcher-campaign-design.md
Normal file
326
docs/superpowers/specs/2026-08-14-launcher-campaign-design.md
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
# Campaign LA — Launcher / Installer / Updater + character-select screen (design spec)
|
||||
|
||||
**Date:** 2026-08-14
|
||||
**Status:** APPROVED design, pre-plan
|
||||
**Plan doc (next step):** `docs/plans/2026-08-14-launcher-campaign.md` (to be written)
|
||||
**Prior decisions consumed:** `claude-memory/project_launcher_direction.md` (2026-08-09)
|
||||
|
||||
Campaign LA is distinct from Modern Runtime **Slice L** (Linux graphical,
|
||||
parked at L1) — "LA" is a campaign identifier in the N/V/P/A/CH/OP/FA
|
||||
series, not a slice.
|
||||
|
||||
## 1. Goal
|
||||
|
||||
One external product — the **acdream launcher** — that is simultaneously
|
||||
the installer, the updater, and the multi-server / multi-account /
|
||||
multi-character session launcher (ThwargLauncher UX model), on Windows and
|
||||
Linux. Plus the one client-side feature the launcher flow exposes as
|
||||
missing: the **retail character-selection screen**.
|
||||
|
||||
Distribution model: alpha users receive ONLY the launcher (per-RID
|
||||
self-contained single-file publish). The launcher fetches the client from
|
||||
the release feed, locates retail DATs, runs the pak bake, and launches
|
||||
sessions.
|
||||
|
||||
## 2. Decisions log
|
||||
|
||||
Pinned 2026-08-09 (NOT re-litigated here):
|
||||
|
||||
| Decision | Value |
|
||||
|---|---|
|
||||
| UI stack | Avalonia (Windows + Linux day one) |
|
||||
| Project split | `AcDream.Launcher` (thin Avalonia) + `AcDream.Launcher.Core` (BCL-only) |
|
||||
| Credentials | **Plaintext file, user-decided.** 0600 on Linux; never in logs/crash bundles |
|
||||
| UX reference | ThwargLauncher (servers × accounts × character pre-select) |
|
||||
| Launch contract | Reuse Slice K1's strict portable config shape |
|
||||
| Paths | Consume Slice L0's `ApplicationPathSet` XDG/Windows contract |
|
||||
|
||||
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 |
|
||||
| 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) |
|
||||
| Profile editing | **Full CRUD in the launcher UI** — add/edit/remove servers, accounts, passwords, per-character settings. The JSON file is storage (hand-editable as a bonus), never the required interface |
|
||||
| Character enumeration | **On-demand probe**: launcher spawns the headless host in a probe mode (connect → `CharacterList` → status event → graceful disconnect BEFORE entering world → exit) and folds the roster into the profile store |
|
||||
|
||||
Rejected: the launcher itself embedding Runtime/Core.Net to speak the
|
||||
game protocol — the character probe runs in the headless host via the
|
||||
normal launch contract, so the launcher stays protocol-free. The probe
|
||||
never enters the world; a graceful account-level disconnect at the
|
||||
character-list stage is the same dance every normal login performs, so
|
||||
the ACE stale-session landmine (hard-killed in-world sessions poisoning
|
||||
the account ~3 min) does not apply on the happy path. Deferred: live IPC
|
||||
fleet dashboard (the status-file format is its forward seam), Create
|
||||
Character, community server-list import.
|
||||
|
||||
## 3. Architecture — file-contract orchestrator
|
||||
|
||||
The launcher never speaks the game protocol and references nothing from
|
||||
the game solution except a new tiny platform assembly. Its contracts with
|
||||
the client are exactly three:
|
||||
|
||||
1. **Config in** — a per-launch session config file (K1 shape, extended).
|
||||
2. **Credential in** — password piped to child stdin (K1 `StandardInput`
|
||||
provider).
|
||||
3. **Status out** — a per-session JSON-lines event file the launcher tails.
|
||||
|
||||
Character enumeration has two feeds, both flowing through the same
|
||||
status-stream vocabulary:
|
||||
|
||||
1. **Cache-from-observation** — hosts report the account's
|
||||
`CharacterList` in the status stream on every login; the launcher
|
||||
folds it into its profile store.
|
||||
2. **On-demand probe** — a "refresh characters" action per account spawns
|
||||
the headless host with a probe-mode session config: connect, receive
|
||||
`CharacterList`, emit the status event, gracefully disconnect
|
||||
**without entering the world**, exit. The launcher folds the roster in
|
||||
exactly as in (1). The launcher refuses to probe an account it is
|
||||
itself currently running a session for; an externally-active session
|
||||
makes the probe fail gracefully (reported on the status stream, never
|
||||
an exception in the launcher).
|
||||
|
||||
A never-seen account can therefore be enumerated before its first real
|
||||
launch, or simply launched in `guiSelect` mode and picked in-client.
|
||||
|
||||
## 4. Components
|
||||
|
||||
- **`AcDream.Launcher.Core`** (new, BCL-only): profile store
|
||||
(load/save/validate/merge-charlist), session-config composition, process
|
||||
spawn + supervision + stdin credential feed, status-event reader,
|
||||
install engine (DAT locate/validate, bake-tool invocation, SHA verify),
|
||||
update engine (manifest client, download, SHA verify, versioned install,
|
||||
pointer swap), self-update stager. Fully unit-testable.
|
||||
- **`AcDream.Launcher`** (new, Avalonia): MVVM shell over Launcher.Core.
|
||||
Server list → accounts → characters tree with **full CRUD in the UI**:
|
||||
add/edit/remove servers (name/host/port), add/edit/remove accounts
|
||||
(account name + password entry), per-character settings editor (launch
|
||||
mode / plugins / login commands), a per-account "refresh characters"
|
||||
probe action, session status column, first-run install wizard, update
|
||||
prompts. Hand-editing the JSON is never required for any flow.
|
||||
- **`AcDream.Platform`** (new, tiny, BCL-only): `ApplicationPathSet` +
|
||||
`IApplicationPathEnvironment` move here from
|
||||
`src/AcDream.Runtime/Platform/ApplicationPathSet.cs`. Runtime, App,
|
||||
Headless, Launcher.Core reference it. Dependency guards (K0 family)
|
||||
amended deliberately in the same commit.
|
||||
- **`AcDream.App`**: gains `--session-config <path>` CLI ingestion into
|
||||
`RuntimeOptions` (env-var dev workflow untouched), the `StandardInput`
|
||||
credential resolver, the status-event writer, the launcher-selected
|
||||
plugin set, login-command execution, and the character-select screen.
|
||||
- **`AcDream.Headless`**: gains the two config fields (`Plugins`,
|
||||
`LoginCommands`), an `idle` consumer policy (enter world, run
|
||||
plugins/commands, stay until stopped), the probe mode (§3/§6), plugin
|
||||
hosting, and the same status-event writer.
|
||||
|
||||
## 5. Profile & credential store
|
||||
|
||||
One JSON file, created and maintained entirely by the launcher UI (the
|
||||
CRUD flows in §4): `ConfigDirectory/launcher-profiles.json`
|
||||
(`%APPDATA%\acdream\` / `~/.config/acdream/`), permissions 0600 on Linux.
|
||||
Hand-editability is a property of the format, not a required workflow.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"servers": [
|
||||
{
|
||||
"name": "Local ACE",
|
||||
"host": "127.0.0.1",
|
||||
"port": 9000,
|
||||
"accounts": [
|
||||
{
|
||||
"account": "testaccount",
|
||||
"password": "testpassword",
|
||||
"characters": [
|
||||
{
|
||||
"name": "+Acdream",
|
||||
"id": "0x5000000A",
|
||||
"launchMode": "gui",
|
||||
"plugins": ["ExamplePlugin"],
|
||||
"loginCommands": ["/tell someone, hi", "/vt start"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `characters[]` = launcher-maintained cache (name/id, fed by status
|
||||
events) + user settings (`launchMode`, `plugins`, `loginCommands`).
|
||||
- `launchMode`: `gui` (straight to world), `guiSelect` (GUI, stop at
|
||||
character-select screen; default when no character chosen), `headless`.
|
||||
- Passwords live in this file and NOWHERE else: never in process
|
||||
arguments, never in session configs, never in logs (K1 redaction
|
||||
discipline extends to the launcher).
|
||||
- Server entries are manual-add (name/host/port). No published-list
|
||||
import this campaign.
|
||||
|
||||
## 6. Launch contract
|
||||
|
||||
**Session config** (written to
|
||||
`CacheDirectory/launcher/sessions/<id>/session.json`): the K1
|
||||
`HeadlessConfiguration` shape extended with:
|
||||
|
||||
- `Plugins: string[]` — plugin names to load from the standard
|
||||
`PluginsDirectory`; hosts load exactly this set.
|
||||
- `LoginCommands: string[]` — ordered chat-typed strings.
|
||||
- Graphical host: `Character` selector may be ABSENT → character-select
|
||||
screen instead of auto-enter.
|
||||
- `Content` descriptor (existing K1 field): `DatDirectory` +
|
||||
`PreparedAssetPath`, filled from the launcher's install records.
|
||||
|
||||
**Spawn:**
|
||||
|
||||
- Headless: `AcDream.Headless --config <path>` (existing CLI).
|
||||
- GUI: `AcDream.App --session-config <path>` (new; parsed once in
|
||||
`Program.cs` into `RuntimeOptions` per code-structure rule 4).
|
||||
- Probe: `AcDream.Headless --config <path>` with a probe-mode session
|
||||
(connect → `characterList` status event → graceful disconnect before
|
||||
`EnterWorld` → exit). Today's config loader requires a character
|
||||
selector and a policy per session (`JsonRequired`); probe mode relaxes
|
||||
that for the probe shape only.
|
||||
- Credential: K1 `StandardInput` provider; launcher writes the password
|
||||
to child stdin then closes it. Headless supports this today; App gains
|
||||
the resolver.
|
||||
|
||||
**Status stream**
|
||||
(`CacheDirectory/launcher/sessions/<id>/status.jsonl`), appended by both
|
||||
hosts, one JSON object per line:
|
||||
|
||||
`started`, `connected`, `characterList` (names + ids + slots),
|
||||
`enteredWorld` (id + name), `pluginLoaded` / `pluginFailed` (name +
|
||||
error), `disconnected`, `exited` (code + reason).
|
||||
|
||||
The launcher tails this for live per-session UI state and folds
|
||||
`characterList` into the profile store. This exact event vocabulary is
|
||||
the seam a future IPC channel (fleet dashboard) replaces — same events,
|
||||
different transport — so event names/payloads are versioned from day one
|
||||
(`"v": 1` per line).
|
||||
|
||||
## 7. Character-select screen (client-side, retail)
|
||||
|
||||
A new pre-world session state between `CharacterList` receipt and
|
||||
`EnterWorld`. Today `LiveSessionController` (`TrySelectCharacter`,
|
||||
`src/AcDream.Runtime/Session/LiveSessionController.cs`) auto-selects and
|
||||
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).
|
||||
- Retail-workflow rules apply: any behavioral deviation ships with its
|
||||
divergence-register row in the same commit.
|
||||
|
||||
## 8. Plugins & login commands on both hosts
|
||||
|
||||
- **Plugin loading** mechanics already live in Core
|
||||
(`src/AcDream.Core/Plugins/PluginLoader.cs`, collectible ALC,
|
||||
`IPluginHost` from `AcDream.Plugin.Abstractions`). This campaign makes
|
||||
the loaded SET session-config-driven on both hosts.
|
||||
- **Headless plugin host:** an `IPluginHost` implementation over Runtime
|
||||
state. UI-only surfaces (`IUiRegistry.AddMarkupPanel`, etc.) become
|
||||
explicit no-ops behind a capability flag so plugins can detect headless.
|
||||
Contract documented in `Plugin.Abstractions`.
|
||||
- **Login commands** run *as if typed into chat*: sequentially, with a
|
||||
default 500 ms inter-command delay (config-overridable per session),
|
||||
starting at entered-world. Failures are logged to the status stream and
|
||||
do not abort the session.
|
||||
- **The parser seam:** chat-string parsing/dispatch (`ChatInputParser`,
|
||||
`ChatCommandRouter`, 152-verb `RetailClientCommandCatalog`) lives in
|
||||
`AcDream.UI.Abstractions`, unreachable from Headless under the K0
|
||||
dependency guard. The campaign extracts the parsing/dispatch CORE to a
|
||||
location both hosts reach — Runtime vs a small shared assembly is
|
||||
decided in the plan after reading the CH seams. The K0 guard amendment
|
||||
is a deliberate, documented change in the same slice. GUI chat behavior
|
||||
must be bit-identical before/after the extraction (CH campaign is
|
||||
closed and user-accepted; this must not reopen it).
|
||||
|
||||
## 9. Installer / updater
|
||||
|
||||
- **First-run wizard:** auto-detect DAT directories
|
||||
(`%USERPROFILE%\Documents\Asheron's Call`, `C:\Turbine\Asheron's Call`)
|
||||
+ manual picker; validate the four DAT files; run bake tool 4 with a
|
||||
real progress UI (~30 GB read); SHA-verify the pak; record
|
||||
`DatDirectory` + `PreparedAssetPath` for session configs.
|
||||
- **Client install/update:** poll the GitHub Releases feed's
|
||||
`manifest.json` (version, per-RID zip URL, SHA-256); download; verify;
|
||||
install to `DataDirectory/app/<version>/`; atomic pointer swap
|
||||
(`current.json`); never while any session is running; keep the previous
|
||||
version for one-step rollback.
|
||||
- **Launcher self-update:** same feed; staged download; rename-dance swap
|
||||
on next start (a running exe can't replace itself on Windows).
|
||||
- **Feed hosting:** GitHub Releases (user-confirmed). Manifest and zips
|
||||
are release assets; the launcher pins the repo/owner in its config.
|
||||
|
||||
## 10. Testing
|
||||
|
||||
- **Launcher.Core unit tests** (new test project, registered in
|
||||
`AcDream.slnx`): profile round-trip + merge, full CRUD operations
|
||||
(add/edit/remove servers/accounts/characters surviving save/load),
|
||||
session-config composition (including the probe shape), manifest/SHA
|
||||
against a local HTTP fixture, process supervision + stdin feed against
|
||||
a fake child, self-update staging.
|
||||
- **Launch contract:** App/Headless suite round-trips — `--session-config`
|
||||
→ `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.
|
||||
- **Headless plugin host:** fixture plugin in the Headless suite
|
||||
(load, capability flag, teardown).
|
||||
- **Connected gates (user-driven):** every launch mode against local ACE
|
||||
(gui / guiSelect / headless), the character probe (fresh account →
|
||||
refresh → roster appears, and repeated probes leaving no stale ACE
|
||||
session), clean-profile first-run wizard end-to-end, staged-manifest
|
||||
update swap, character-select visual matrix, delete flow,
|
||||
login-commands + plugin observable behavior on both hosts, and
|
||||
add-server/add-account flows done purely through the UI.
|
||||
|
||||
## 11. Risks / open items for the plan phase
|
||||
|
||||
1. Parser-extraction landing spot (Runtime vs shared assembly) — decide
|
||||
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?).
|
||||
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.
|
||||
7. `AcDream.Platform` extraction touches K0-family dependency guards —
|
||||
amend the guard assertions in the same commit, never loosen silently.
|
||||
8. Windows profile-file permissions: 0600 is Linux hygiene; Windows keeps
|
||||
default user-profile ACLs (no extra hardening — accepted plaintext
|
||||
posture).
|
||||
9. Probe semantics: verify against ACE source (and one live check) that a
|
||||
graceful disconnect at the character-list stage leaves no lingering
|
||||
account session — the design assumes the landmine is exclusive to
|
||||
hard-killed in-world sessions. Also verify ACE's behavior when a probe
|
||||
hits an account with an externally-active session (reject vs boot),
|
||||
and make the probe's failure path graceful either way.
|
||||
10. Probe config shape: the headless loader's `JsonRequired` character
|
||||
selector + policy need a deliberate relaxation for probe sessions
|
||||
only — normal sessions keep strict validation.
|
||||
Loading…
Add table
Add a link
Reference in a new issue