acdream/docs/plans/2026-07-22-gamewindow-slice-8-checkpoint-g-runtime-settings.md
Erik fec0d94148 refactor(settings): own two-phase runtime settings
Move pre-window loading, startup application, live settings mutation, toon context, quality reapply, and SettingsVM loans behind one RuntimeSettingsController. Preserve retail command behavior, ordered target publication, draft semantics, and retryable failure convergence while removing duplicate GameWindow state and feature bodies.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 13:30:22 +02:00

7.6 KiB

GameWindow Slice 8 — Checkpoint G Runtime Settings Plan

Objective

Replace GameWindow's duplicated pre-window and OnLoad settings paths with one two-phase RuntimeSettingsController. The controller owns persistence and live settings state; render, window, audio, UI, and streaming objects remain borrowed targets. This is a structural cutover, not a redesign of the settings format or retail option behavior.

Two carried behaviors are now explicit rather than accidental. Saved FOV is applied during startup even when developer tools are disabled; previously it was incorrectly gated on construction of SettingsVM. The named-retail /framerate live toggle and notice remain intact, while acdream's already shipped cross-launch ShowFps persistence is documented as AP-121 rather than being attributed to the retail command body.

Frozen behavior and ordering

  1. Construct the controller before Window.Create and load Display, Audio, Gameplay, Chat, and the default Character bag exactly once.
  2. Resolve the preset plus environment overrides once for the immutable startup snapshot. WindowOptions.VSync comes from the pacing policy initialized with that snapshot; WindowOptions.Samples comes from the same resolved quality.
  3. After window/GL input, camera, DAT effects, and optional audio exist, but before devtools or world/render factories, apply startup settings in this order: monitor-aware pacing, resolution/fullscreen, field of view, then audio.
  4. Downstream terrain, dispatcher, render-range, and streaming factories read the same startup quality snapshot. The legacy startup stream-radius override remains at its current composition boundary.
  5. Bind one complete RuntimeSettingsTargets only after window, UI (when enabled), terrain atlas, dispatcher, render-range, and streaming exist. Binding performs no display, quality, UI-lock, or audio replay.
  6. Settings-panel Save preserves its callback order: keybindings, Display, Audio, Gameplay, Chat, Character. Each section retains its existing persistence/error boundary.
  7. Display Save persists first, applies resolution/fullscreen, publishes the new display snapshot, then reapplies quality. A changed MSAA sample count is logged as restart-required and is not applied to the live GL context.
  8. Runtime quality reapply preserves the existing order: alpha-to-coverage, terrain anisotropy, render-range publication, streaming reconciliation, completion budget.
  9. /framerate, UI lock, loot permits, and combat preferences mutate the controller's canonical state, synchronize the optional Settings draft using the existing draft-preservation rules, and retain their existing save-error behavior.
  10. Enter-world changes the active toon key, loads that character bag even when devtools are disabled, and publishes it to the optional Settings view model. Session reset restores the startup/default character context before the later identity-reset operation restores the key to default.
  11. Per-frame display/audio preview reads the optional Settings draft through a typed source. Particle range and FPS visibility read the same source so an unsaved draft still previews exactly as it does today.
  12. Shutdown first withdraws the Settings view-model and runtime-target loans; the controller never disposes the window, renderer, UI, streaming, audio, or input objects it borrows.

Types and ownership

  • IRuntimeSettingsStorage is the test seam for the five settings bags. JsonRuntimeSettingsStorage is the production owner of the one concrete SettingsStore and exposes that store only for retained-window layout persistence.
  • RuntimeSettingsSnapshot is the immutable startup value.
  • IRuntimeSettingsStartupTarget receives the one startup Display/Audio apply.
  • IRuntimeSettingsTargets receives future display-window, quality, and UI-lock changes. BindRuntimeTargets stores it without invoking it.
  • IRuntimeSettingsPreviewSource exposes current Display/Audio preview values and whether a live draft exists.
  • RuntimeSettingsController owns persistence, current values, active toon, resolved quality, and the optional SettingsVM binding. It also implements the combat settings source; no second gameplay-state mirror remains.
  • SilkRuntimeDisplayWindowTarget, RuntimeSettingsStartupTargets, and RuntimeSettingsTargets are App-layer adapters over borrowed runtime objects.

Automated gates

  • Construction loads every bag once and populates one immutable snapshot.
  • Startup apply is ordered, exactly once, and independent of GL in controller tests.
  • Runtime target binding produces zero calls; unbinding prevents future target calls while state and persistence still advance.
  • Display Save ordering, quality transition, and restart-required MSAA logging.
  • Quality target receives one complete resolved value per reapply.
  • Draft preview/cancel, external FPS mutation, UI lock, loot permits, and combat option synchronization.
  • Toon activate/load/reset/save behavior with and without a Settings view model.
  • Save failures preserve each established state/target boundary.
  • Source-boundary test proves GameWindow no longer constructs or loads a SettingsStore, owns persisted settings fields, or contains quality/display feature bodies.
  • Focused App tests, full App tests, Release solution build, and full Release suite.

Review and commit gate

After the focused tests pass, the existing retail-conformance, architecture/integration, and adversarial reviewers inspect the complete diff in parallel. Confirmed findings are corrected and the same reviewers repeat until clean. Documentation, roadmap, issues, memory, CLAUDE.md, and AGENTS.md are reconciled in the same bisectable Checkpoint G commit.

Implementation result

  • RuntimeSettingsController is constructed before Window.Create; all five bags load once and WindowOptions consumes its immutable startup snapshot.
  • Startup substeps commit independently, so an audio failure retries audio without replaying already successful pacing/display/FOV work.
  • Late target binding is inert and reversible; expected-instance SettingsVM withdrawal cannot detach a replacement binding.
  • The concrete quality adapter preserves all five application steps and the unchanged MSAA restart boundary.
  • Combat changes merge their three fields into a live Gameplay draft rather than overwriting unrelated unsaved fields; successful external changes also advance the SettingsVM persisted baseline so Cancel cannot resurrect stale command state.
  • UI-lock target, persistence, and baseline publication form one explicit convergence state. A transient failure leaves the same requested value retryable instead of letting the equality fast-path strand unfinished work.
  • Enter/reset/toon changes load and publish Character state independently of developer-tool availability.
  • GameWindow no longer constructs or loads SettingsStore, owns persisted settings mirrors, or contains display/quality/settings feature bodies.
  • Candidate metrics: 3,663 raw lines / 162 fields / 37 methods, down 394 lines / 36 fields / 17 methods from Checkpoint F and 12,060 lines (76.7%) from the campaign baseline.
  • Thirty-five focused App settings/boundary tests, all 43 SettingsVMTests, the UI Abstractions Release suite (534 pass), and the App Release suite (3,183 pass / 3 intentional skips) pass. All three independent corrected-diff reviews are clean. The production App Release build has zero warnings/errors; the solution Release build retains the 17 existing #228 test-project warnings; and the complete Release suite passes 7,553 tests / 5 intentional skips.