Skip to content

feat(sid): pick tunes the C64's own SID chips can play - #272

Merged
kfox merged 1 commit into
mainfrom
feat/sid-tune-match-host-chips
Aug 12, 2026
Merged

feat(sid): pick tunes the C64's own SID chips can play#272
kfox merged 1 commit into
mainfrom
feat/sid-tune-match-host-chips

Conversation

@kfox

@kfox kfox commented Aug 11, 2026

Copy link
Copy Markdown
Owner

What

Follow-up to #271. That PR made c64cast describe a machine's internal SID chips accurately. This one acts on the description in the only place it can: tune selection.

On a link that can't re-place chips, a tune the machine doesn't suit stays that way — the chip is what it is, and the resolved-audio verdict can only say so. But a waveform scene whose file spec resolves to a directory or glob already picks a random candidate per setup(), and it can pick with the declared chips in mind.

[hardware]
host_sid_chips = { d400 = "6581" }
host_sid_tune_match = "prefer"
Value Behavior
off (default) The pool is untouched.
prefer Fitting tunes tried first; the rest stay as a fallback tail.
require Non-fitting tunes dropped from the pool.

A tune fits when the declarations would produce a clean verdict for it — right model on every chip, and a chip declared at every address the tune drives. So a single-SID machine stops landing on 2SID tunes whose second voice-set goes nowhere, and a 6581 machine stops landing on tunes composed on an 8580.

Design notes

The predicate shares the verdict's renderers. host_chip_fit calls describe_declared_chips / describe_declared_audio rather than re-deriving the match. A picker that disagreed with the line logged moments later would be worse than no picker at all.

Only the PSID header is read (_HOST_FIT_HEADER_BYTES = 0x80 — the header ends at $7C and the extra-SID address bytes are its last relevant fields), so ordering an HVSC-sized directory costs short reads, not a load per tune.

Three no-opinion cases leave the pool exactly as it was:

  • a link that reads the real SID state (the U64 re-places chips per tune, so no tune is a poor fit);
  • an assumed model — the NTSC/PAL convention is weak enough to warrant a warning but not strong enough to justify dropping files out of someone's directory. The verdict may act on a guess; selection won't;
  • nothing declared at all.

Default is off for the same reason: a directory the user pointed at is a statement of what they want played, and quietly narrowing it is their call.

require still falls back to the unfiltered pool, with a WARNING, when nothing fits — a mis-declared machine surfaces as a log line rather than a scene that can never start.

The two declarations buy different things. host_sid_chips describes the whole machine, so an undeclared tune address fails — that is what skips 2SID tunes. host_sid_model judges the primary chip's model alone: it names a chip without claiming it is the only one, and inferring a chip count from it would reject working tunes on a machine whose second chip we were never told about.

The shuffle happens first, so the grouping is a bias on a random order rather than a reordering that would make one tune the deterministic pick for a whole class of machines.

Verification

make check (ruff, mypy --strict, pyright, 3729 tests) and make site-check green.

29 new/changed tests: the predicate across all declaration shapes and no-opinion cases (test_sid_resolved.py), profile resolution (test_backend.py), field validation (test_config.py), and pool ordering end-to-end through scene construction (test_waveform.py) — prefer/require/off, 2SID skipping, the assumed-model no-op, the require fallback warning, and an unreadable candidate sorting to the back.

Also exercised offline against real HVSC files, which the synthetic test headers don't cover:

  • a 6581/8580 mixed directory picks the matching model under both declarations;
  • a single-6581 machine under require reduces a pool of four real 2SID tunes plus one 1SID tune to the 1SID tune;
  • a declared 6581+8580 pair at $D400/$D420 correctly rejects both the 6581+6581 and 8580+8580 2SID tunes;
  • an assumed model leaves the order byte-identical.

No hardware run: this change adds no device I/O. It selects a different file before the existing player path runs, and everything downstream of the pick is untouched.

A tune the machine doesn't suit stays that way on a link that can't
re-place chips — the chip is what it is, and the resolved-audio verdict
can only say so. The free variable is which tune: a waveform scene whose
`file` spec resolves to a directory or glob already picks one at random
per setup(), and it can pick with the declared chips in mind.

[hardware].host_sid_tune_match adds "prefer" (fitting tunes first, the
rest as a fallback tail) and "require" (misfits dropped). A tune fits
when the declarations would produce a clean verdict for it, and
host_chip_fit routes through the same renderers as that verdict rather
than re-deriving the match — a picker that disagreed with the line
logged moments later would be worse than no picker. Candidates are
judged from the PSID header alone, so an HVSC-sized directory costs
short reads rather than a load per tune.

Three cases return no opinion and leave the pool untouched: a link that
reads the real SID state (it re-places chips per tune), an assumed
NTSC/PAL model (weak enough to warn on, not strong enough to drop files
out of someone's directory), and nothing declared at all. Default is
"off" for the same reason — a directory the user pointed at is a
statement of what they want played. "require" falls back to the full
pool with a warning when nothing fits, so a mis-declared machine
surfaces as a log line, not a scene that can never start.

Only host_sid_chips can skip a 2SID tune: host_sid_model names one chip
without claiming it is the only one, and inferring a chip count from it
would reject working tunes on a machine whose second chip we were never
told about.
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.15686% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.02%. Comparing base (76cb29a) to head (8d420d7).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
c64cast/sid/sid_resolved.py 83.33% 1 Missing and 1 partial ⚠️
c64cast/sid/waveform.py 93.75% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #272      +/-   ##
==========================================
+ Coverage   82.00%   82.02%   +0.02%     
==========================================
  Files         142      142              
  Lines       24655    24705      +50     
  Branches     3612     3622      +10     
==========================================
+ Hits        20219    20265      +46     
- Misses       3645     3647       +2     
- Partials      791      793       +2     

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

@kfox
kfox merged commit fd5e0a4 into main Aug 12, 2026
19 checks passed
@kfox
kfox deleted the feat/sid-tune-match-host-chips branch August 12, 2026 02:49
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