Game: player-facing graphics settings screen (quality, vsync, frame cap) at runtime

Goal

A player-facing settings screen in the shipped game: resolution, vsync, frame cap, anti-aliasing level, post-processing, shadow quality, and the rest of the scalability knobs — changeable at runtime and persisted per user.

Why it isn't #160 (closed)

#160 (closed) wires ProjectSettings.graphics.quality into the renderer and exposes per-feature toggles in Studio, for the developer, applied at game startup. That is the plumbing this needs, but it stops at the editor: the shipped game reads a preset once and the player can never change it.

The missing pieces are all outside #160 (closed)'s scope:

  • An in-game UI — a .uidoc settings screen, so this depends on the Guinevere GUI epic (#47).
  • Runtime application. #160 (closed) applies the feature set at startup. Changing MSAA live means rebuilding the scene pipeline cache (pipelines bake in the sample count); changing render scale means reallocating targets. Both are safe between frames but neither is wired.
  • Per-user persistence, separate from the project's authored defaults. #42 wants the same thing for input rebinding and calls it a "settings profile" — these should share one mechanism rather than grow two.
  • Vsync and frame cap at runtime. Today vsync is fixed at window creation: ProjectSettings.graphics.vsyncGameBuild → the generated main.ziggpu.Window.createSDL_GPU_PRESENTMODE_VSYNC. The generated loop has no frame limiter at all, so a cap needs building, not just exposing. Studio already does the live half (GpuRenderer.requestVsync/applyPendingVsync swap present modes between frames) — the game can reuse that shape.

Scope

  • A default settings .uidoc shipped with the engine that a project can use as-is or replace.
  • A runtime render.Features apply path (from #160 (closed)) that is safe to call between frames, including the pipeline-cache rebuild for MSAA.
  • A frame limiter in the generated main loop, for capping below the refresh rate.
  • Per-user persistence, shared with #42's profile.

Depends on

  • #160 (closed) — the feature set and its presets
  • #47 — the in-game GUI this would be authored in
  • #42 — the settings-profile persistence

Note

Worth confirming the shipped game's 60 fps is vsync before sizing this: docs/decisions/bistro-perf-baseline.md traces the code path, and the renderer measures ~12.8 ms (~78 fps) on the same scene with vsync off.