Commit 537c971a by PLN (Algolia)

docs(log): 019 — the ghost that cut the new track dead, and 32 buttons only an ear could catch

Captain's-log entry for the 2026-07-29 session, written while it is fresh (the
armada/tasks discipline: these logs are the raw material for posts and video
narration, not bureaucracy).

Two findings, one class: a machine check stayed green while the instrument was
broken. One was Tidal's (xfadeIn turns the outgoing pattern down but never off, and
a gain-0 event still broadcasts its cut group). One was mine, shipped the previous
evening (the button remap assigned cells in reading order, inverting 32 gate/gesture
pairs that only PLN's ear caught).

The shareable core, for later: a gain of zero is not the absence of an event. In a
system where events carry side effects — voice stealing, global FX bus writes — the
silent ones still act. Plus the green-check problem twice in one day, convergence as
a bug report, and the honest limit of verdict-equality checking.
parent be52627d
---
log: 019
title: "The ghost that cut the new track dead and 32 buttons only an ear could catch"
date: 2026-07-29
task: "#102 #94 #100 (with #13, #35, #84, #99)"
tags: [tooling, gig, opal2026, measurement, livecoding, tidal-internals]
shareable: true
---
## Cap (what & why)
OPAL is J-7 and PLN was recording takes, so every finding arrived as a symptom
heard mid-set rather than a bug report. Two of them turned out to be the same
class: **a machine check had been green while the instrument was broken.** One was
Tidal's fault. One was mine, shipped the previous evening.
## Manœuvre (how)
**The fade.** PLN had been losing orbits all afternoon — *"when i leave d5 on the
33 effcet, it fades in oblivion!"* — and had found a workaround before I found the
cause: *"so for now hush then ctrl+enter seems like a fix ahah but not a lovely one
:D"*. The sentence that cracked it was his: *"reboot-then-ctrl-enter: doesnt seem
to obliviate, loops forever. sounds like a track-to-track state bug"*. Same code,
two outcomes, differing only by **pattern history** — and `streamHush` prepends
`silence` to exactly that history.
From there it was reading, not guessing. Tidal's `xfadeIn` is one line; `|*` takes
structure from the left; so the outgoing pattern keeps every event it ever had.
Confirmed by `queryArc` rather than by argument, then cross-checked against
SuperDirt's `DirtEvent.sc` to find out whether a gain-0 event is actually harmless.
It is not.
**The buttons.** PLN heard it twice in ten minutes: *"techno drum mask is inverted
on d8"*, then *"drums in revolution seem way too fast ... yea def an inversion"*.
I measured the damage on my own commit's diff instead of on the corpus, which is
the difference between "how many odd sites exist in 826 files of history" and "how
many did I just break".
## Prise (findings / artifacts)
**The ghost (`be52627`).** Every `dN` is a 4-cycle `xfadeIn`. It turns the outgoing
pattern DOWN but never OFF: measured, **at cycle 64 of a 4-cycle fade it still
emits one onset per cycle, at gain 0**. And SuperDirt broadcasts the cut group in
`playSynths` (`DirtEvent.sc:182-189`) *before* amplitude is ever read — `~amp`
appears later, at line 160, as a gate-synth argument — with `cutAllSamples: 1` for
a positive `cut`. So the silent ghost kept releasing the incoming pattern's voice
on every single hit. The orbit was not fading out. **It was being cut to death by
its own predecessor.**
Onsets per cycle, incoming/outgoing, fading at cycle 0 over 4:
```
xfadeIn 4 1/1 1/1 1/1 1/1 | 1/1 1/1 1/1 1/1 ghost forever
clutchIn 4 0/1 0/1 1/0 1/0 | 1/0 1/0 1/0 1/0 clean, never overlaps
xfadeCut 4 1/1 1/1 1/1 1/1 | 1/0 1/0 1/0 1/0 clean from cycle 4
```
Fix = `playFor now (now+t)` on the outgoing pattern, envelopes untouched.
`clutchIn` is arguably better for a cut-heavy rig; it is deliberately NOT the
default, because changing the feel of every transition 7 days before a gig is not
a repair.
**The buttons (`5089eec`, `2c178c9`, `6ed637d`).** Phase 2 of the remap assigned
each orbit's two button cells in *first-appearance order*. Tracks write the gesture
line above the gate line. So: **32 inversions, 13 files**, nearly all in the
setlist. 81 cells then moved onto the row their role belongs to, across 39 files,
with `silent-eval --seeded` verdicts byte-identical twice and alignment still
83/83.
**Two lints PLN asked for by name.** PV010 (role on the wrong row) and PV011 (one
FX bus slot shared by two orbits — *"like shared busses they screw things"*). PV011
immediately found `crushbus 41` shared by take_5's bass and choir.
**Delegated, landed:** the per-stream highlight colours (`2c0524e`) and the HUD
telling the rig which track is loaded (`2576e77`).
## Sel (the shareable learning)
**A gain of zero is not an absence of an event.** That is the whole bug. Turning a
pattern down is not the same as stopping it, and in a system where events carry
side effects — voice-stealing, global FX bus writes — the silent ones still act.
Tidal's crossfade is correct about loudness and wrong about consequence.
**The green-check problem, twice in one day.** `check-boot.sh` had four passes and
all four stayed green on the broken transition: it typechecked, it parsed the way
Pulsar chunks it, and it emitted events. The only check that can see this bug is
one that *counts onsets from the outgoing pattern after the fade* — so that became
Pass 5, and it is mutation-tested, because a gate you have never seen fail is not a
gate. Meanwhile the highlight wiggle had been "fixed" weeks ago in a stylesheet
that could not finish the job: the user copy won the cascade per-property, but **a
cascade winner cannot unset a property it never mentions**, so the `border` in the
package sheet underneath kept reflowing the line.
**Convergence as a bug report.** The role classifier was wrong three times, and
each time the tell was the same: apply the repair twice and it disagreed with its
own previous pass. `mask . chop` is a gate. `superimpose (struct ...)` is NOT — the
`struct` is two levels down building the added layer. And a body that opens on the
next line reads as "unknown", which is dangerous rather than neutral, because an
unknown control gets a positional fallback that re-creates the inversion.
**And the honest limit of the gate I trusted.** All 81 moves passed verdict
equality — soundly, but narrowly. Every button CC seeds to 0, so permuting CCs
between two calls in one block cannot change either default branch *by
construction*. When a swap turned `("^59" - "^91")` into `("^91" - "^59")` I nearly
"fixed" a non-bug; the check could not have told me either way, because `0-0=0`
whichever order it is in. **Verdict equality at the seed answers "did this silence
anything at boot" and nothing else.**
## Hameçon (hook)
"My livecoding rig kept killing tracks the moment I loaded them. The fix was `hush`
first — and the reason was that the *previous* track was still playing, silently,
murdering the new one on every beat. A gain of zero is not silence. It's an assassin
with the volume down."
## Sillage (what it unlocks)
- #102: PLN reboots and records a full set — the one test only ears can run.
- #13 (transitions) has a solid floor now, so the remaining work is craft: choose
per-situation transitions, and decide post-OPAL whether `clutchIn` should be the
default for a rig where most orbits own a cut group.
- #77 is untouched by all of this and now clearly separate: an orbit the incoming
track never *declares* gets no eval, so no transition, so it just keeps playing.
- take_5's d4 fade is still unexplained — it has no cut group, so at least one more
mechanism exists. Named rather than absorbed into the win.
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