The autonomous behavior stack
August 24, 2026 · View on GitHub
The brain is the biggest untracked gap in the parity audit (§03): the runtime's
autonomous.rs exists nowhere in the daemon and no design doc owns it yet. This file is the
holding pen — what the port has to cover, and the ideas from the theremin/chorale work
(2026-08) that should land as behaviors in that stack rather than as more ad-hoc modes.
To port from the runtime (audit §03)
A 16-state machine — Chill, LookAround, Wander, TurnInPlace, Zoomies, Startle, Stretch, Ruffle, Preen, Sneeze, Dance, GroundPick, Nap, BallPlay, Petted, Held — built on:
- an energy/mood model driving state choice
- novelty-grid exploration memory for Wander
- ToF obstacle avoidance with freshness gating
- contrast-based startle; sound reactions (noise vs voice) with self-audio / self-motion gating
- ball play (approach / line up / kick), a nap cycle, petting reactions
- the in-process gamepad↔autonomous toggle (DpadRight 2 s),
--autonomous-max-speed
Its inputs all exist in the daemon now: ambient sound events (step 2 — logged, no consumer),
depth frames (step 3), classified trunk-frame obstacle points (step 5), and the voice tags
(step 2, "ready and waiting for the brain step"). Held depended on pickup detection, which
is deprecated — decide whether to revive it or drop the state.
New inputs the brain gets for free (2026-08 work)
Things the theremin/chorale steps built that the runtime's brain never had:
- Nearby ducks, by stable id — the chorale beacon (
ChoraleBeacon), which survives BLE address rotation (seedocs/../memory: never key on the address) - A shared beat with no clock sync (
sounds::chorale::beat) — ±20 ms across ducks - RSSI per advertisement — free coarse distance (near / far / approaching)
- ~245 spare bytes of extended-advertising payload
- A live synth voice (
sounds::Stream): pitch/level/vowel at runtime, not just bank wavs - Hand distance from the ToF (
kinematics::hand::Tracker)
Behavior ideas, roughly by charm-per-line
Social (BLE presence — the new territory):
- Recognition & greeting — keep a persisted list of duck ids met;
greeta stranger, a warmer sound for a friend, apeck/sigh when a friend's beacon goes stale. Friendship as a met-count that changes the greeting over time. Reads as memory; needs no sync. - Lonely / content — a duck alone calls out occasionally; a duck with company doesn't. An input to the mood model, not a state.
- Excitement on approach — RSSI rising for a known id → visible anticipation.
- Marco Polo — one duck hidden, another guides you by quacking faster as RSSI grows.
- Follow-the-leader — RSSI holds spacing, ToF handles the duck directly ahead.
- Applause / social feedback — one duck does a roulade, nearby ducks react.
- Telephone — a message hops duck to duck through the spare payload, mutating.
- Voting — preferences in the beacon payload; majority picks the group's next behavior.
Beat-synced motion (cash in the sync work where it shows — no speaker involved):
- Group head-bob / sway — all ducks in phase on the shared beat; visual sync tolerates ~50 ms where audio wanted 20. Group pose on the downbeat of a bar. Conga line.
- Dance (already a runtime state) becomes synchronized dance when company is present.
Musical, beyond the chorale:
- Call and response — antiphonal phrases; the gap between phrases hides sync error, so it is easier than the chorale and more duck-like.
- A round — same melody, deliberate multi-bar offsets; the offsets absorb the error.
- One role each — drone + rhythmic peck + melody. Small speakers do texture better than harmony.
Duck detector (camera + NPU) — wanted, waiting on mediad
A tiny single-class detector for our own duck: precise bearing for gaze and following,
which neither ToF nor BLE can give. The RK3566 has a 0.8 TOPS INT8 NPU (rknpu2 /
rknn-toolkit2); a YOLOv8n/11n-class model at 320 input should run ~20–40 ms → 15–30 Hz,
leaving the CPUs alone. Range math: IMX219 ~62° HFOV, a 25 cm duck is ~25 px at 3 m — room
scale, which is the interaction envelope.
- Gate first: is the NPU driver on the board? (
dmesg | grep -i rknpu) — vendor-kernelrknpu2, not mainline. And/dev/rgafor free NV12 resize. Ask mediad to tee raw NV12 from the ISP mainpath; the detector must not decode the streaming MJPEG. - Data is the project, not the model. Duck's-eye-view footage (robot height, robot camera) auto-labeled by a big open-vocab model, distilled into the tiny one; synthetic renders from the Open Duck CAD for the tail; hard negatives (rubber ducks, white prints).
- Fusion: vision cannot tell identical ducks apart — camera = direction, ToF = distance,
BLE beacon = identity + presence. Follow-the-leader uses all three; "look at each other
when doing stuff" = beacon says when, detector says where,
robot.lookdoes the rest. - Same architecture as
tofd/pet-detect: a perception worker outsiderobotd, safe to kill. Fix the placeholder IMX219 intrinsics during the mediad port (audit §04 TODO).
Shape notes for the port
- Presence, mood, and the shared beat are inputs to one brain, not modes beside it — the chorale/theremin grew as explicit modes because there was no brain to hang them on; fold them in as states/inputs when it lands ("Petted" is to pet-detect what "Sing" is to a heard beacon).
- The chorale ends up as a spontaneous event, not a command.
robotctl choraleis bench scaffolding: once ducks run autonomously, a group of them together should sometimes break into song on their own — a low random chance gated on company being present (and plausibly on mood/energy), the way Zoomies or Dance fire, not something a user starts. Rare on purpose: a surprise duet is a delight, a jukebox is not. The mechanism barely changes — an idle-beacon duck already knows who is nearby, so "decide to sing" is one more transition;[chorale] acceptstays as the consent gate for whether a duck may ever join in. - The chorale's consent rule generalizes: anything social is opt-in and off = invisible
(
[chorale] accepttoday; probably one[social]switch tomorrow). - Recognition/greeting is the right first behavior: highest charm per line, no sync, and it exercises the BLE discovery layer — the part still under hardware suspicion.