v1.9.0

August 18, 2026

This release adds controller stick calibration and a broad internal quality pass. Controllers: calibrate the analog stick range of each controller with live visual feedback, persisted per controller, plus fixes for SDL controllers — digital L2/R2 now drive analog triggers for cores like Flycast, and duplicate controllers after a reconnect are reconciled. While a game runs, the display and system stay awake. The live shader editor's Apply now survives relaunching the same game, and TV Mode keeps the system you selected after exiting a game. Under the hood, pause state was consolidated into a single enum (fixing scrub and recording bugs), core options follow a single five-layer precedence walk, controller mappings share one source of truth, and a new automated smoke-test harness boots 11 ROMs through the real launch pipeline to verify stability.

Highlights

  • Stick calibration: Calibrate each controller's analog stick range per direction with a live calibration session and visual feedback, applied to all your games and persisted per controller.
  • No sleep while gaming: The display and system are kept awake while a game is running, so play sessions are never interrupted by your Mac falling asleep.
  • Live shader Apply persists: Shader changes applied with the in-game shader editor survive relaunching the same game instead of reverting to the stale snapshot.
  • TV Mode keeps its system: After exiting a game, TV Mode stays on the system you selected rather than snapping back to Recent.
  • SDL controller fixes: Digital L2/R2 buttons now drive the analog trigger state (fixing Dreamcast controls in Flycast), and duplicate controllers are reconciled when GameControllerKit reconnects a device SDL already accepted.
  • Consolidated pause state: Rewind, scrub, and pause now share a single state machine — scrubbing over a paused game keeps it paused on exit, and gamepad navigation restores the prior pause state.
  • Automated smoke tests: A new boot-smoke harness launches 11 ROMs (NES to MAME) through the real launch pipeline and asserts the first frame renders, with the slang shader audit still healthy.

Detailed Changes

  • feat: implement stick calibration for controllers — StickCalibration and ControllerCalibration models for per-direction analog stick range, StickCalibrationSession for live capture, SDLInputManager applies calibration, runners (DOSRunner, ScummVMRunner) consume it, calibration UI in ControllerSettingsViewGroup, en/es/ja/pt translations, plus a ROMLibrary.refreshAll bulk-refresh refactor
  • fix: prevent SDL duplicate when GCController registers a controller after reconnect — reconcile on GC/SDL connect notifications by closing the duplicate SDL handle; drop the never-decremented acceptedSDLCountByName counter that made the name-based guard permanently fail
  • fix: preserve live shader Apply across game relaunch — merge playtime bookkeeping into the current library entry instead of the stale launch-time snapshot; add the missing shader.applyAndSave key to all translation files
  • fix: prevent display and system sleep while a game is running
  • refactor: apply audit slices D, E and F-sl — launch-path work deduplication (cached CoreCapabilities, save-list snapshot, generic XPC reply helper, normalized extension index), state/precedence consolidation (5-layer core-option walk, single controller mapping map, ShaderManager as the single slang-preset source, canonical RA match status, pending-theme cluster, ManualStatusController), and tooling (11-ROM boot-smoke matrix via TRUCHI_SMOKE_ROM, slang audit, retired outdated MAME scripts)
  • refactor: remove dead LaunchConfig fields and single wizard flag — slotToLoad/coreOptions/bezelFileName/hardcoreMode deleted; hasCompletedWizard dropped in favor of has_completed_onboarding
  • refactor: consolidate pause state into a single PauseState enum — replaces the manually-synced isPaused/isRewinding triple; scrub over paused keeps paused on exit, gamepad toolbar restores prior pause state, setPaused no-ops during scrub
  • refactor: apply audit slices A+C — single AuthState enum, recording session reset on all teardown paths, honor 404 negative-cache expiry, remove dead SequenceRunner engine and write-only flags; fix scrub/empty-recording bugs
  • fix: send analog trigger state for SDL digital L2/R2 buttons — pure SDL controllers only set the digital key state; cores like Flycast read Dreamcast triggers from the analog state; add l2/r2 to the flycast core button overrides and labels
  • fix: keep TV mode on selected system after game exit — make initialEntryID a one-shot seed so rebuilds no longer snap row-1 selection back to the entry TV mode opened on
  • Bump marketing version to 1.9.0 and build number to 32 across project.yml, both Info.plists, and docs/_config.yml