Debugging and panic recovery in Hydra
Learning objectives
- learner can recognise and fix the five silent-failure patterns in Hydra: missing `.out()`, bare reactive values, unclamped FFT reads, feedback gain at-or-above 1, and wrong output-buffer selection
- learner can trace any Hydra failure to one of two error surfaces — throw to `#err` or silent black/frozen/blown-out canvas — and choose the correct diagnostic step for each
- learner can work within the rig's 4-bin Strudel shim: tag patterns with `.analyze('hydra')`, stay within `a.fft[0..3]`, and substitute band thresholding for absent onset/RMS/centroid features
- learner can recover a crashed or blown-out sketch live using the panic-safe pattern, and explain why frame buffers survive across re-evals while JS state does not
Capstone — one whole task that evidences the objectives
Take a deliberately broken Hydra file seeded with one bug from each silent-failure class: (1) a chain that never renders because `.out()` is missing; (2) a reactive `a.fft` value baked in because its `() =>` thunk is missing; (3) a `src(o0)` feedback loop that whites out because gain is not contracted below 1; (4) an out-of-range `a.fft[5]` read returning NaN; (5) a Strudel voice missing its `.analyze('hydra')` tag so every bin reads zero; (6) a buffer object (`o0`/`s0`) chained bare instead of wrapped in `src(...)`; (7) a chain rendered to `o1` that never appears because `render(o1)` was not called; (8) a `.rotate().kaleid()` vs `.kaleid().rotate()` transform-order mistake; and (9) an array argument the author expected to ramp smoothly but that steps once per BPM cycle. Without looking up solutions, diagnose each failure using the two-surface model — check `#err` first, and where it is empty read the canvas (black / frozen / blown-out) and match it to its pattern class — then fix all nine bugs. Because the rig shim exposes no onset/RMS/centroid, add one audio-reactive element that derives a pseudo-onset from a threshold on `a.fft[0]`. Finally, from memory, write a panic sketch satisfying all four safety constraints (guaranteed `.out()`, every reactive value in a `() =>` thunk, FFT reads clamped to 0..3, non-zero base terms), and — leaning on the fact that re-eval runs `hush()` then re-evaluates the whole file on the same GL context so frame buffers survive — verify it recovers the canvas live in under five seconds. Deliver one annotated Hydra file naming, per bug, the error class and the canvas symptom that led you there, and labelling each panic-sketch line with the safety constraint it satisfies.
Prerequisite modules
This module is built around a single professional reality: in an algorave or club set, a Hydra crash does not give you a dialog box or a stack trace. It gives you a black screen, a frozen frame, or a canvas washed out to white — and an audience. The ability to read that canvas like a diagnostic display, trace the failure to its class, fix it in seconds, and keep the set alive is what separates a working Hydra performer from someone who stops and restarts.
The twenty atoms here fall into two interlocking layers. The first layer (L1 gotchas) covers the bugs you hit on day one: chains that silently build textures into the void without .out(), reactive values that bake in as constants because the thunk wrapper is missing, FFT reads that return NaN because the index is out of the rig’s 4-bin range, feedback that races to white because gain is not contracted below 1, and buffers rendered to o1..o3 that never appear because render() was not called. These are not conceptually subtle — they are mechanical invariants the rig enforces silently — so the first drills are simple recognition-and-fix pairs timed under two minutes each.
The second layer (L5 debug) teaches you to use the rig’s error surface systematically. Hydra errors split into two classes: those that throw a JavaScript exception (visible in the on-page #err element, canvas frozen on the previous frame) and those that produce no throw at all (canvas goes black, frozen, or blown-out, #err stays empty). This matters because the debugging procedure is different for each: a throw tells you what broke; a silent failure tells you what the canvas is doing, and you must reason backward to the cause from the five known patterns. “Check #err first; if empty, read the canvas and match it to the pattern” is the complete diagnostic loop.
The rig-specific FFT layer introduces a constraint that most Hydra tutorials ignore: the a object here is a 4-bin shim fed from Strudel, not a native microphone FFT. Native tutorials that call a.setBins(8) and read a.fft[7] silently return undefined → NaN, blanking the output. The fix is enforcing the 0..3 range contract and tagging the Strudel pattern with .analyze('hydra') — without which all bins read zero regardless of what the audio is doing. Higher-level features (onset, tempo/beat-phase, RMS, spectral centroid) are absent from the shim; a performer who needs them must synthesize an approximation from the four bins, for example using a threshold on a.fft[0] as a pseudo-onset.
Re-eval semantics enter here because they explain why the panic recovery works the way it does. Each save calls hush() then re-evaluates the whole file on the same GL context — there is no page reload. Frame buffers o0..o3 survive across evals (which is why feedback loops keep running after you edit the file), but JS state does not. A JS syntax error throws and freezes the canvas on the last frame, which means that even a broken file is recoverable: fix the syntax and save to resume. The panic pattern exploits this — the minimal solid(0.05, 0.05, 0.08).out() can always be typed or pasted in under five seconds and will always succeed, resetting the canvas to a dark, non-erroring state from which the performer can rebuild.
The capstone exercises all four objectives together under no-scaffold conditions. The learner receives a seeded bug file (available in context/L5-debug/hydra-bugfile.hydra), works through the diagnostic loop without hints, and must write their own panic sketch from memory. The annotation requirement forces explicit reasoning: the learner names the error class for each bug, states which canvas symptom led them there, and labels each line of the panic sketch with the safety constraint it satisfies. This documentation doubles as a personal recovery protocol the performer can revisit before any set.
Walkthrough
Hydra’s failures are silent — a black or white screen with no error. This lesson drills the diagnostic loop: symptom → bug class → fix, one class at a time, each shown as the corrected patch. Open the Hydra editor, run with Ctrl-Shift-Enter, hush() clears. Each fence is a complete, running patch.
1 — the reset patch (memorise it). The first rule of a black screen: have something you can paste blind that always shows pixels, giving you a floor to rebuild from. This is your panic button — commit it to muscle memory ([[hydra-panic-solid-with-safe-reactive]]).
osc(10, 0.1, 0.8).out(o0) // always renders something — your recovery floor
2 — symptom: black screen. Class: no .out(). The most common silent failure — a chain that computes but is never routed to a buffer renders nothing, with no error. Fix: every visible chain ends in .out(o0) ([[hydra-out-required-for-render]]).
noise(3).color(0.2, 0.6, 1).out(o0) // FIX: the .out(o0) that was missing
3 — symptom: white-out. Class: uncontracted feedback. A src(o0) feedback loop that adds energy without contracting it below 1 blows up to pure white within a second. Fix: scale the fed-back signal down (here .color(0.94…)) so the loop decays into trails instead of exploding ([[hydra-feedback-gain-contraction]]).
osc(6).add(src(o0).color(0.94, 0.94, 0.94), 0.5).out(o0) // contracted feedback → stable trails
4 — symptom: reactivity frozen. Class: baked value (missing thunk). .rotate(a.fft[0]) reads the bin once, at evaluation, and freezes it. Hydra re-runs the function you give it every frame — so a live value must be wrapped in a () => thunk ([[hydra-thunk-for-live-values]]).
a.setBins(4)
osc(20, 0.1, 0.6).rotate(() => a.fft[0] * 3).out(o0) // FIX: () => re-reads every frame
5 — symptom: black/garbage flicker. Class: NaN from a bad read. Reading a.fft[5] on a 4-bin shim returns undefined → NaN → the whole frame corrupts. Fix: guard the index (|| 0) and clamp the value so one bad number can’t nuke the screen ([[hydra-no-value-clamping]], [[hydra-analyze-tag-required-for-bins]]).
a.setBins(4)
const b = (i) => Math.min(a.fft[i] || 0, 1) // guard + clamp
osc(20, 0.1, 0.6).brightness(() => b(0) * 0.3).out(o0)
6 — the defensive patch (the capstone). Everything a performance patch needs to not fail live: guarded bins, contracted feedback, a base brightness so it never goes fully black, and a single .out(o0). Build this way and a wrong keystroke degrades gracefully instead of killing the projection ([[hydra-src-wrapper-required]], [[hydra-error-surface-modes]]):
a.setBins(4)
const b = (i) => Math.min(a.fft[i] || 0, 1)
osc(18, 0.08, () => 0.5 + b(1) * 0.3)
.rotate(() => b(2) * 2)
.add(src(o0).color(0.94, 0.94, 0.94), 0.4) // contracted feedback trails
.brightness(0.05) // base term: never fully black
.out(o0)
What good looks like. A performer’s recovery is fast and calm: you recognise the symptom (black vs white vs frozen), you know its class, and you either fix in place or paste the reset patch and rebuild — the projector never sits dead. The beginner failure is freezing on a black screen with no reset patch ready. Keep the reset patch in a comment at the top of every set, and write chains defensively (guard, contract, base term) so failures degrade instead of crash. (Skill map: live-visualist Domain E — recover without killing the projection; the reset patch is the signature survival skill.)
Now make it yours. Type each bug deliberately (delete an .out(), bake a thunk) and practise recognising the symptom before you fix it. Time yourself pasting the reset patch from memory. Add a second reset patch with a different look. Wrap your whole set in defensive helpers so no single line can white-out.
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Audio-Visual Performer — integrated, synced live AV — The integrated set (north star) required
- Live Visualist — zero to performing live-coded & generative visuals — Perform the set — live-coded, generative, audio-reactive visuals for an audience required
- VJ — visual performance with projection, light & video — Play out: the full VJ show at scale recommended