acdream/docs/plans/2026-08-25-launcher-content-stabilization.md
Erik af9327a17b
Some checks failed
CI / linux-portable (push) Failing after 3m12s
CI / windows-gate (push) Failing after 6m35s
CI / release (push) Has been skipped
feat(launcher): stabilize prepared content updates
2026-08-25 19:17:13 +02:00

10 KiB

Launcher content stabilization

Date: 2026-08-25 Status: IMPLEMENTED Goal: make prepared-content updates fast, explicit, and safe without turning the launcher into a package manager.

User contract

  1. The launcher window appears before network access, full-file hashing, baking, recovery, or any other potentially long operation.
  2. Ordinary startup reads only small metadata: the install record, pak header, file length/write time, and the verification sidecar when present.
  3. No long content operation begins silently. The launcher first names the reason, work kind, approximate disk requirement, and whether the existing installed game remains usable.
  4. A prepared-content change uses a small locally generated overlay whenever the affected DAT IDs/landblocks are bounded. A full rebuild is an explicit, rare fallback for format changes or extraction changes with unbounded impact.
  5. The launcher never starts a mixed client/content pair. Cancellation or a bake/publication failure preserves the prior pair. Once approved content is ready, an unavailable or failed matching-client update leaves Play disabled and retains the verified content for a cheap retry.

What exists already

  • acdream.pak has a 64-byte header containing DAT iterations, format version, and BakeToolVersion (the current content-recipe identity).
  • install.json records the pak SHA-256, size, DAT path, and recipe identity.
  • install.verification.json avoids the former 24-second startup hash when size/write-time still match. Explicit Verify files remains the full-hash path.
  • acdream-bake already accepts --ids and --landblocks, and a filtered bake produces an ordinary valid pak with only those typed keys.
  • IPreparedAssetSource and IPreparedCollisionSource are the existing renderer/physics seams; no consumer needs to know which mapped pak supplied a key.
  • Launcher and client are published together, and the launcher payload already includes the matching bake executable.

Deliberately small model

There are only four work kinds:

Kind Launcher behavior
None No content prompt.
Overlay Build one cumulative overlay containing all keys changed since the base recipe.
FullRebuild Explain the long rebuild and required free space before starting.
Verify User-requested or exceptional recovery hash; always visible and cancellable.

The release-feed schema remains unchanged for the first implementation. Every published build already updates the launcher before the client. The updated launcher carries the matching content requirement and a small compiled migration catalog. This avoids stranding strict schema-1 launchers on a feed shape they cannot parse. A future independently versioned content feed can replace the catalog without changing the runtime content model.

BakeToolVersion is retained on disk for compatibility but is treated as a content recipe version, not an executable build number. It changes only when the produced prepared content changes.

On-disk content state

The existing install.json remains the base-pak authority and is not extended; older launchers reject unknown fields. New state lives in the optional sidecar DataDirectory/pak/content.current.json:

{
  "schemaVersion": 1,
  "baseSha256": "<sha256 from install.json>",
  "effectiveRecipeVersion": 6,
  "overlay": {
    "path": "acdream-update-6.pak",
    "sha256": "<64 lowercase hex>",
    "size": 123,
    "recipeVersion": 6
  }
}

Rules:

  • The sidecar is valid only when baseSha256 binds it to the current base record and every path is a safe canonical filename beneath the pak directory.
  • At most one overlay is active. A later overlay is cumulative and atomically replaces the prior sidecar; there is no unbounded lookup chain.
  • The base and overlay must name the same installed DAT iterations and pak format. The base may carry an older recipe; the overlay carries the effective recipe.
  • Missing overlay keys fall through to the base. A present-but-corrupt overlay key is authoritative corruption and never falls through.
  • Render and collision reads follow the same ordering and share the same two memory mappings.
  • An absent sidecar means the base pak is the complete active content set.
  • content.client-pending is a separate, tiny crash-safe activation gate. A content migration creates it before touching content and removes it only after client compatibility is confirmed. It deliberately carries no package graph; existence means “do not publish this content to Play yet.”

Migration catalog

One compiled catalog entry describes each recipe transition:

target recipe
work kind
player-facing reason
affected DAT IDs and/or landblocks (overlay only)

To update a base from recipe 5 directly to recipe 7, the launcher asks the catalog for the cumulative 5 -> 7 impact and emits one recipe-7 overlay. If any step is FullRebuild, the combined migration is a full rebuild. A missing catalog step fails closed with an explanatory error; it never guesses.

The recent procedural night-sky change is None because it changed client shader/code only. A future addition of bounded prepared sky keys can be Overlay. A global mesh-extraction correction such as recipe 5's solid-face change is FullRebuild.

Update transaction and UI

The launcher keeps the existing one-question update surface. When the candidate client needs newer content, pressing Update first opens the content-work confirmation:

World data update required This release adds prepared sky assets. acdream will build a small update from your installed Asheron's Call files. The existing game stays installed until this finishes. Estimated work: overlay / approximately N files / M free space required. Update now · Later

After confirmation:

  1. Validate the remembered DAT directory and free-space floor.
  2. Build to a transaction-owned candidate path while the active content stays untouched.
  3. Validate pak header/TOC and compute the new artifact's SHA once. Never hash the unchanged base as part of an overlay update.
  4. Atomically publish the content sidecar.
  5. Install/activate the compatible client.

The newly prepared content is not published to the launch orchestrator until the startup check confirms that the active client is compatible or the client update succeeds. Choosing Not now, losing the network, or failing the client download therefore cannot launch the old executable against the new pak. The launcher keeps the verified content on disk and resumes at the much smaller client-update step.

For FullRebuild, the same transaction builds a candidate base beside the old base, verifies it, then atomically swaps the base record/file. It never moves the playable base out of place before the long build starts.

Progress uses the existing strict Bake JSONL protocol and shows phase, percentage, failures, and ETA. Cancellation returns to the launcher without changing active content.

Startup ordering

App.OnFrameworkInitializationCompleted must not synchronously wait on LoadExistingAsync before constructing MainWindow. It constructs the shell with an explicit Checking installation state, assigns/shows the window, then starts content discovery on the UI dispatcher. Feed update checking begins only after that cheap discovery completes, preventing two startup modals from racing.

If an exceptional recovery path really needs a full base hash, the shell is already visible and says exactly what it is doing. Launch stays disabled until the recovery check finishes, but the application never looks frozen.

Compatibility and rollback

  • A client session receives the resolved base path plus zero or one overlay path. Old clients continue receiving only the base.
  • The client validates the effective recipe before constructing world owners.
  • The updater does not activate a client whose content requirement is unsatisfied.
  • Choosing Later leaves the old client/base pair active.
  • Client rollback is allowed only when the selected client accepts the active content set; otherwise the launcher explains the required content rollback or rebuild instead of launching an incompatible pair.

Verification gates

  • Launcher window construction test proves no installer/hash task is awaited before the main window is assigned.
  • Quick-discovery tests cover missing sidecar, matching sidecar, missing cache, changed length/time, recipe mismatch, and exceptional visible verification.
  • Content-state tests cover path containment, base-digest binding, atomic publication, cancellation, and crash residue.
  • Composite-source tests cover overlay hit, base fallback, authoritative overlay corruption, render/collision parity, stats, and balanced disposal.
  • Session-config round trips cover base-only and base+overlay on App and Headless.
  • Update tests prove prepared content cannot become launchable before client compatibility is confirmed; Not now and client-download failure remain fail-closed, while bake/candidate failure preserves the prior pair.
  • Release solution compilation and the affected Launcher, Content, App, and Headless gates remain green.

Implementation checkpoint

Implemented 2026-08-25:

  • The Avalonia window is assigned and opened before content discovery, client recovery, feed access, or exceptional hashing begins.
  • Ordinary current-install discovery uses metadata/header/cache checks; the explicit verification command owns visible whole-pak hashing.
  • Recipe migrations are compiled and cumulative. Bounded migrations build one filtered overlay; unbounded/global migrations use the explicit candidate full-rebuild path. The current recipe 4 -> 5 transition is correctly a full rebuild because the solid-face extraction change is global.
  • Base/overlay reads are unified for render and collision with overlay-first, Missing-only fallback and authoritative corruption.
  • Content activation is bound to client compatibility in memory and through content.client-pending, so Not now, failed download, process crash, and launcher restart cannot expose a mixed pair.

Final Release gates:

  • dotnet build AcDream.slnx -c Release: 0 warnings, 0 errors.
  • Launcher UI/ViewModels, excluding the documented manual desktop lane: 82/82.
  • Launcher.Core Windows-compatible suite: 360/360.
  • Hermetic Content suite: 130/130.
  • Affected App layered/session composition: 35/35.
  • Affected Headless configuration: 9/9.