Skip to content

Machine-settings layer + canonical user data dir - #135

Merged
kfox merged 1 commit into
mainfrom
machine-settings-layer
Jul 19, 2026
Merged

Machine-settings layer + canonical user data dir#135
kfox merged 1 commit into
mainfrom
machine-settings-layer

Conversation

@kfox

@kfox kfox commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a machine-local settings overlay applied to every run type, and moves persisted machine-specific data (DAC calibrations, WLED + loop presets) to a canonical user data dir — so c64cast works identically from a repo checkout, a pip install, or a future PyPI wheel. This retires the four Path(__file__).resolve().parent.parent repo-anchored globals, which were a latent bug for any non-editable install.

New c64cast/paths.py (stdlib-only, no intra-package imports)

Single source of truth for locations, resolved at use time so env overrides + tests work:

  • settings_path()~/.config/c64cast/settings.toml (XDG-aware; %APPDATA%\c64cast\ on Windows; $C64CAST_SETTINGS overrides)
  • data_root()/calibration_dir()/presets_dir()/loop_presets_dir()~/.local/share/c64cast (XDG; %LOCALAPPDATA%; $C64CAST_DATA_DIR overrides)
  • legacy_data_root() → the old repo anchor, only for doctor's mv hints

Machine settings (config.py)

load_machine_settings/apply_machine_settings overlay the file as the lowest layer above dataclass defaults, hooked into load, load_master (the ensemble master-defaults Config gets the overlay first, and apply_master_defaults takes a machine-overlaid baseline so master TOML still beats machine settings), and quickcast.build_config. Precedence:

defaults → machine settings → project/per-system TOML → master cascade → CLI → env

[[scenes]]/[ensemble] in the file are rejected (it holds cross-run defaults, not playlists). The per-section apply loop is factored into _apply_toml_sections so the machine file and project file share identical code (unknown-key warnings, validations, the [color] special case).

--save-settings (cli.py)

A config-free command that persists this invocation's machine-relevant flags — -u/--url (decomposed via connect.parse_connection_uri), -d/--device, --sid-model, -s/--system — into the settings file: merges with any existing file, writes it sparsely + atomically, prints it, exits 0. Nothing savable → exit 2. $C64CAST_URL never auto-saves; the DMA password can never be written.

Data-dir consolidation

dac_calibration, transport, and wled_device now resolve their paths through paths.*; save_calibration uses the shared atomic writer.

Doctor

_probe_machine_settings + _probe_data_dirs in the ENVIRONMENT section report the resolved settings + data locations and — from a source checkout with old files still at the legacy calibration//presets/ dirs — the exact mv to migrate them. No implicit migration.

Tests

New test_paths + test_save_settings; machine-settings precedence coverage across config/ensemble/quickcast; the previously-missing C64CAST_DMA_PASSWORD env-precedence test; dir-redirect tests switched from the removed module globals to $C64CAST_DATA_DIR. A shared MachineSettingsIsolation helper keeps the load/round-trip suites hermetic against a real settings file on the developer's machine.

No config-schema change (make schema is a no-op).

Verification

  • Full gates green: ruff, mypy --strict (config.py included), pyright, 2601 tests, schema-drift.
  • No-hardware end-to-end: --save-settings writes a sparse/annotated/secret-free file and merges; --doctor ENVIRONMENT shows settings + data dir + legacy mv hints; quick playback inherits the settings connection with no -u.
  • Real hardware smoke: settings-driven playback rendered correctly on a real U64 with no -u (connection sourced from the settings file).

Introduce a machine-local settings overlay applied to every run type, and
move persisted machine-specific data to a canonical user data dir, so
c64cast works identically from a repo checkout, a pip install, or a PyPI
wheel (no more Path(__file__).parent.parent repo-anchoring).

New c64cast/paths.py (stdlib-only, no intra-package imports) is the single
source of truth for locations:
  - settings_path(): ~/.config/c64cast/settings.toml (XDG-aware;
    %APPDATA% on Windows; $C64CAST_SETTINGS overrides)
  - data_root()/calibration_dir()/presets_dir()/loop_presets_dir():
    ~/.local/share/c64cast (XDG; %LOCALAPPDATA%; $C64CAST_DATA_DIR)
  - legacy_data_root(): old repo anchor, only for doctor's mv hints

Machine settings (config.py): load_machine_settings/apply_machine_settings
overlay the file as the lowest layer above dataclass defaults, hooked into
load, load_master (ensemble master defaults + a machine-overlaid cascade
baseline so master TOML still beats machine settings), and
quickcast.build_config. Precedence: defaults -> machine settings ->
project/per-system TOML -> master cascade -> CLI -> env. [[scenes]] and
[ensemble] in the file are rejected. The per-section apply loop is factored
into _apply_toml_sections so the machine file and project file share code.

--save-settings (cli.py): a config-free command that persists this run's
-u/--url, -d/--device, --sid-model, -s/--system into the settings file
(sparse + atomic write, merges with existing, prints, exits 0; nothing
savable -> exit 2). $C64CAST_URL never auto-saves; the DMA password is never
written.

Data-dir consolidation: dac_calibration, transport, and wled_device now
resolve their paths through paths.* at use time; save_calibration uses the
shared atomic writer.

doctor: new _probe_machine_settings + _probe_data_dirs in the ENVIRONMENT
section report the resolved settings + data locations and, from a source
checkout, the exact mv to migrate legacy calibration/preset files.

Tests: new test_paths + test_save_settings; machine-settings precedence
coverage in config/ensemble/quickcast; the previously-missing
C64CAST_DMA_PASSWORD env test; dir-redirect tests switch from the removed
module globals to $C64CAST_DATA_DIR. A shared MachineSettingsIsolation test
helper keeps the load/round-trip suites hermetic against a real settings
file on the dev machine.

Docs (CLAUDE.md, docs/architecture.md, docs/usage.md) + the calibration/
and presets/ READMEs updated. No config-schema change.
@kfox
kfox merged commit 533a8e3 into main Jul 19, 2026
8 checks passed
@kfox
kfox deleted the machine-settings-layer branch July 19, 2026 16:11
@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.42857% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.56%. Comparing base (1070ea8) to head (b326307).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
c64cast/cli.py 81.81% 5 Missing and 1 partial ⚠️
c64cast/paths.py 84.61% 4 Missing and 2 partials ⚠️
c64cast/config.py 94.44% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #135      +/-   ##
==========================================
+ Coverage   80.37%   80.56%   +0.19%     
==========================================
  Files          86       87       +1     
  Lines       19700    19843     +143     
  Branches     2947     2969      +22     
==========================================
+ Hits        15834    15987     +153     
+ Misses       3187     3163      -24     
- Partials      679      693      +14     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant