Commit 6981bd15 by PLN (Algolia)

fix(pvlint): PV008 is a WARNING — linked percussion is an idiom, not a defect

PLN, on lime_tacos' ^44 driving both d3 (hats) and d6 (cymbales sol):

    "lime tacos its ok when two percs are linked, e.g. here, change rythm of
     both 3 and 6. [...] im fine with this, not a NOGO, just a WARN?"

He is right, and the interesting part is WHY the rule was wrong rather than that
it was. PV008 was earned on a real bug — the 2026-07-29 phase-2 remap left d1
holding ^42 after d2 was given ^42, and perfect.tidal shipped one button firing
two orbits. That bug is real and this rule is how it was found.

But the accidental case and the deliberate one are TEXTUALLY IDENTICAL. Two
orbits, one button CC, no way to tell from the source whether PLN meant it. The
rule had quietly encoded "one button drives one orbit" as an invariant of his
style, when it is only a hypothesis about it — and the hypothesis was wrong. He
groups percussion on purpose: one gesture re-rhythms the whole group.

A check that cannot distinguish intent must not hold the gig gate shut. It
reports; PLN decides. So: severity error -> warning, and the detail now states
both readings and what distinguishes them (deliberate percussion grouping vs a
control inherited from a remap) instead of assuming the second.

Effect: tools/gig-up.sh goes NO-GO -> GO. This was the last blocking failure, and
it was blocking on a correctly-written track.

Added test_pv008_is_a_warning_not_an_error, which quotes PLN's ruling, so nobody
"tightens" this back to error without meeting the argument first. pvlint 63/63.
parent efb1aff7
......@@ -452,6 +452,23 @@ def pv008_button_drives_two_orbits(track: Track) -> Iterable[Finding]:
Family controls are excluded on purpose: gF1-3 and gMute1-3 are per-FAMILY
by design (measured across the corpus), so sharing them is correct.
DEMOTED TO WARNING 2026-08-01, by PLN, and the reason matters more than the
severity. This fired on lime_tacos, where ^44 drives d3 (hats) and d6
(cymbals), and he ruled it correct:
"lime tacos its ok when two percs are linked, e.g. here, change rythm
of both 3 and 6. [...] im fine with this, not a NOGO, just a WARN?"
So a shared button is a ParVagues IDIOM — one gesture re-rhythms a whole
percussion group — and this rule cannot tell that apart from the 2026-07-29
accident it was written for. Both look identical in the source. A check that
cannot distinguish intent must not hold the gig gate shut: it reports, PLN
decides. ([[feedback_stated_invariant_is_a_hypothesis]] — "one button, one
orbit" was a hypothesis about his style, and it was wrong.)
The rule still earns its place: it is how the perfect.tidal mis-migration was
found, and a genuinely accidental double-drive is worth a look every time.
"""
owners: dict[int, dict[int, int]] = {} # cc -> {orbit: first line}
for orb in track.orbits():
......@@ -472,14 +489,17 @@ def pv008_button_drives_two_orbits(track: Track) -> Iterable[Finding]:
for _orbit, line in sorted(per_orbit.items()):
yield Finding(
rule="PV008",
severity="error",
severity="warning",
line=line,
message=f"button ^{cc} is driven by {where}",
detail="One press fires both orbits. Nothing errors and nothing "
"is silent, so this only shows up as an unexplained extra "
"layer mid-set. Give the over-budget orbit its own control, "
"or comment the surplus gesture out with a FIXME until "
"gSel can fold it onto one control.",
detail="One press fires both orbits. DELIBERATE if these are "
"percussion layers you want re-rhythmed together (PLN's "
"lime_tacos idiom) — leave it, and a comment saying so "
"spares the next reader this decision. A MISTAKE if the "
"second orbit inherited the control from a remap: then it "
"shows up mid-set as an unexplained extra layer, because "
"nothing errors and nothing is silent. To separate them, "
"give the over-budget orbit its own control.",
)
......
......@@ -226,10 +226,29 @@ def test_pv008_flags_one_button_driving_two_orbits():
'd2 $ midiOn "^42" (mask "t f")\n')
f = lint(src, "PV008")
assert len(f) == 2 # one per orbit needing an edit
assert all(x.severity == "error" for x in f)
assert "^42" in f[0].message and "d1, d2" in f[0].message
def test_pv008_is_a_warning_not_an_error():
"""PLN 2026-08-01, on lime_tacos' ^44 driving d3 (hats) and d6 (cymbals):
"its ok when two percs are linked, e.g. here, change rythm of both 3 and
6. [...] im fine with this, not a NOGO, just a WARN?"
A shared button is a deliberate idiom — one gesture re-rhythms a percussion
group — and the source is IDENTICAL to the accidental case this rule was
written for. A check that cannot tell intent apart must not hold the gig gate
shut. This test exists so nobody "tightens" it back to error without meeting
that argument first.
"""
src = ('d3 $ midiOn "^44" (mask "t(4,8,1)")\n\n'
'd6 $ midiOn "^44" (<| "~ cy ~ cy")\n')
f = lint(src, "PV008")
assert len(f) == 2
assert all(x.severity == "warning" for x in f), \
"PV008 must stay advisory — PLN's percussion-linking idiom is not a defect"
def test_pv008_one_orbit_using_a_button_many_times_is_fine():
"""Three references from ONE orbit is one gesture, not a collision."""
src = ('d1 $ midiOn "^42" (<| "k k")\n'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment