docs: Campaign LA plan — slices LA0-LA11, recon-grounded; spec corrections
Plan doc with twelve slices, dependencies, review protocol (Opus dual-lens: architectural + retail-faithful), and ledger. Three parallel recon reports grounded the slice bodies: - Retail select screen is gmCharacterManagementUI: flat listbox + Enter/Delete/Restore + dialogs. NO 3D preview (that machinery is chargen-only gmCG3DView) — the spec 3D-preview slice is deleted, the old retail-ui/05-panels.md pedestal claim is uncited and wrong. Restore + CharacterError join scope; delete sends account+slot. - Chat-command core (parser/router/catalog/ChatVM) is dependency-clean BCL+Core; extraction to Runtime is a move, not a rewrite. - Probe reuses the NoCharacters early-exit shape (graceful teardown at the CharacterList stage exists today); roster plumbing is new. - Bake tool needs --progress-json + explicit --out; no whole-file SHA exists — launcher records/verifies its own. - UI Studio is deleted (Campaign V) — stale references corrected. Roadmap + CLAUDE.md Current state carry the campaign pointer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
afb4e82943
commit
9b0bb25581
4 changed files with 484 additions and 26 deletions
14
CLAUDE.md
14
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
388
docs/plans/2026-08-14-launcher-campaign.md
Normal file
388
docs/plans/2026-08-14-launcher-campaign.md
Normal file
|
|
@ -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 <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 | — | | | |
|
||||
| LA1 | — | | | |
|
||||
| LA2 | — | | | |
|
||||
| LA3 | — | | | |
|
||||
| LA4 | — | | | |
|
||||
| LA5 | — | | | |
|
||||
| LA6 | — | | | |
|
||||
| LA7 | — | | | |
|
||||
| LA8 | — | | | |
|
||||
| LA9 | — | | | |
|
||||
| LA10 | — | | | |
|
||||
| LA11 | — | | | |
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue