apirrone d7b6f15398 The duck chorale, end to end: two ducks in a room sing together
`robotctl chorale` and they find each other over Bluetooth, work out who sings what,
and sing — with no shared clock, no pairing, no network, and nobody in charge.

**Off by default.** `[chorale] accept` in robotd.toml, and it is the whole section:
a chorale moves the mouth and the head, so a duck that started animating because
another duck walked into the room would be doing motion nobody asked for, in
someone's living room. Off also means *invisible* rather than politely declining —
an un-opted-in duck puts nothing on the air at all, so it is not even enumerable.

**How the work divides.** `btd` owns the radio and does no thinking: it advertises
bytes it was handed and reports bytes it heard. `robotd` owns the behaviour and the
voice and touches no radio. Between them is `chorale.*` on one connection carrying
both directions — a subscription down saying what to advertise, notifications up
saying what arrived (API v14).

`btd` reports an **age**, not a timestamp, and that is load-bearing: the two daemons
share a machine but not an epoch, and an age survives a socket in a way another
process's clock reading does not. `robotd` subtracts it on arrival.

**The conductor owns the seating, and that is not a convenience.** Seating depends
on join order, so a duck seating *itself* from whatever it happened to hear
disagrees with one that heard a different subset — and both then sing alto. So the
conductor keeps the roster, broadcasts it, and everyone replays `seat_all` over it.
One source of truth, which is what a conductor is for. A duck not yet in the roster
does not guess a part; it waits to be seated on the next beat, which is what makes
joining free rather than negotiated.

Found while testing: a duck that walks out of range must keep its **seat**. Pruning
it would shift everyone below onto a different part — the one harm the roster exists
to prevent — so its line goes unsung instead, exactly as in a choir somebody left,
and only the *voice count* drops.

**Singing is a function of time, not of samples played.** The loop publishes where
the ensemble is, in beats, and the writer thread turns that into notes. So a duck
whose audio stalls resumes in the right place rather than a bar behind, and one that
joins mid-piece comes in where the piece actually is.

Also here:

 - The bass fix asked for by the duck-speaker test, now applied on the robot too:
   the writer sets a 300 Hz speaker rolloff, so a low note is carried by harmonics
   the driver can make instead of a fundamental it cannot.
 - `robotctl chorale`, a live readout that names the part once it is settled so it
   survives in the scrollback.
 - One PCM opener and one `SIGPIPE` mask shared by the theremin and the chorale,
   which were the same code twice.
 - A cheatsheet section written in the order the questions actually come up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 17:41:02 +02:00
..