Opus dual-lens review ofcb6502c8passed with six findings; this lands the fix round: 1. headless-portability.yml: AcDream.Platform src/tests join both path triggers and the presentation-free build/test arrays — the moved XDG tests run on ubuntu-latest again (they had fallen out of every Linux lane). 2. acdream-architecture.md: AcDream.Platform gets its own layer block; Runtime may-reference clause updated (the guard changed incb6502c8, its human-readable twin had not). 3. PlatformDependencyBoundaryTests: the BCL-only contract (zero project/package references) is now enforced, not just observed. 4. memory/project_linux_graphical.md canonical seam renamed. 5. Plan LA0 recon corrected: the K0 Headless guard was never the guard needing amendment (it asserts Headless own refs); Runtime own-refs guard was — the commit did the right thing, the plan text now says so. 6. App declares its AcDream.Platform reference explicitly per its own convention instead of riding transitivity. Platform.Tests: 4 passed (3 moved + the new guard). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
418 lines
23 KiB
Markdown
418 lines
23 KiB
Markdown
# 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.
|
||
|
||
## Linux posture (binding — user decision 2026-08-14)
|
||
|
||
Everything the launcher does must WORK ON LINUX in this campaign, except
|
||
GUI client launches: the Linux graphical client is Slice L, parked at L1,
|
||
resuming later ("ok we will do it later"). Concretely:
|
||
|
||
- **Linux-shipping in LA:** the Avalonia launcher UI, profile CRUD +
|
||
0600-permission file, installer (manual DAT picker — the auto-detect
|
||
paths are Windows-only; `acdream-bake` is GL-free and runs on Linux),
|
||
updater (staged swap; Linux can replace a running binary but keep the
|
||
same staged-atomic flow), headless launches with plugins + login
|
||
commands, and the character probe.
|
||
- **Launcher UX on Linux:** the `gui` / `guiSelect` launch modes render
|
||
disabled with an explicit "requires the Linux graphical client (Slice
|
||
L)" note — never a silent failure.
|
||
- **Per-slice enforcement:** every slice touching Launcher.Core, Headless,
|
||
Runtime, Bake, or Platform runs its test projects on Linux (native
|
||
Ubuntu or WSL, matching the K-slice practice) before the slice is DONE;
|
||
LA4/LA9/LA10 additionally prove a real `linux-x64` self-contained
|
||
publish. LA11's connected-gate script gets a Linux section: launcher on
|
||
Ubuntu doing CRUD, probe, headless launch with plugin + login commands,
|
||
first-run install with a manual DAT path, and an update swap.
|
||
- When Slice L later ships, the launcher's Linux GUI modes light up with
|
||
NO launcher changes (the session-config contract is host-agnostic) —
|
||
that expectation is part of LA's design acceptance.
|
||
|
||
## 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/`;
|
||
`GraphicalLegacyConfigurationMigratorTests.cs` fixtures), and the dependency
|
||
guards — CORRECTED post-review (the original recon here asserted the wrong
|
||
guard, the C4-closeout failure mode): the K0 Headless guard
|
||
(`HeadlessAssemblyReferencesOnlyTheRuntimeProject`) asserts HEADLESS's own
|
||
csproj reference list, which this move does not touch — it stays UNCHANGED;
|
||
the guard that actually needs amending is Runtime's own
|
||
`RuntimeDependencyBoundaryTests.RuntimeProjectDeclaresOnlyApprovedProjectDependencies`
|
||
(Runtime gains the `AcDream.Platform` reference), amended with a cited
|
||
comment in the same commit. 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 <path>`:** 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 <dats> --out <DataDirectory>/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/<version>/`, 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 | review PASS; fix round applied | `cb6502c8` + fixes | Opus PASS w/ 6 findings 2026-08-14; narrow re-review pending | Byte-identity proven; CI Linux lanes + arch doc + Platform self-guard + App explicit ref fixed; plan recon corrected |
|
||
| LA1 | in flight (Sonnet) | | | pinned contract v1 + 5 optional fields |
|
||
| LA2 | — | | | |
|
||
| LA3 | in flight (Sonnet, isolated worktree) | | | pinned contract shared with LA1 |
|
||
| LA4 | — | | | |
|
||
| LA5 | — | | | |
|
||
| LA6 | — | | | |
|
||
| LA7 | — | | | |
|
||
| LA8 | — | | | |
|
||
| LA9 | — | | | |
|
||
| LA10 | — | | | |
|
||
| LA11 | — | | | |
|