Skip to content

Hunter bird: a blob-tracker shader Setup, with the blob-records contract - #159

Merged
charliehuge merged 12 commits into
mainfrom
claude/hunter-bird-mockup
Nov 5, 2026
Merged

charliehuge merged 12 commits into
mainfrom
claude/hunter-bird-mockup

Conversation

@charliehuge

@charliehuge charliehuge commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Adds hunter_bird, a FundingCAPTCHA shader Setup that draws the blob tracker's notes as a bird hunting visitors, and the groundwork it needs: a design mockup, a blobs channel source for the shader player (ADR-0022), and the spec and plan. The Setup is in the rotation and plays for 120 s.

What's in it

1. The hunter_bird Setup — ShowControl/FundingCAPTCHA/shaders/hunter_bird/

  • Buffer B holds the bird's state for each slot: interest, lock, which person it has struck, its reticle, mood, blinks, and footprints.
    • The bird strikes each person once, then ignores them.
    • Lock builds only when motion is above LOCK_ON (0.65) and the person is staying put: each slot's velocity, low-passed over 1.5 s, must be near zero. A walker's stride swings their box as much as waving arms do, so drift is what tells walking from flailing. Walking at any speed or stride tested never strikes; flailing strikes in 2.4–4.2 s.
    • A target whose blob is missed for a camera frame keeps the bird; it isn't handed to a rival.
    • A person who stands still loses the reticle. The box is smoothed before its size change is measured, so a still person escapes up to ±8 px of box jitter per side.
  • Image draws the world (ground, footprints, people with hatching, canopy), the field notes and reticle, the strike stamp with its punch-in, and the eye (iris band, lids, blink membrane).
    • Pixels hidden by the lids skip the world, and footprints stop at a slot's first dead print. Both are for Pi frame rate.
  • All tuning is in common.glsl constants.
  • docs/FundingCAPTCHA.md lists the Setup in "Setups in rotation".

2. Design mockup — docs/superpowers/specs/2026-11-04-hunter-bird-mockup.html

  • A runnable canvas sketch of the approved look and behaviour. Open it in a browser; it has a scrubber.

3. A blobs channel source for the shader player (ADR-0022, amends ADR-0021)

  • Setups bind "blobs" and read an 8×3 RGBA32F texture with texelFetch. Each slot holds a box, centroid and velocity, an id, a state (tentative, confirmed, coasting or lost) and seconds in that state. A slot stays with the same person for that person's whole track.
  • body_blobs.py finds blobs in the Body itself, so boxes line up with the silhouette, and tracks them with BlobStabilizer in Body pixels. It runs at camera rate, and only while the current Setup reads blobs. Boxes are eased at render rate between camera frames. Tunables are in shader_app.blobs.
  • BlobStabilizer.update_tracks() reports every live track, its detection, and the ids that expired. update() is unchanged.
  • shaders/blob_painter.glsl stands in for the tracker in Shadertoy. On the Pi, --test-input reads the painted Body back from the GPU and runs the real tracker on it.
  • shaders/blob_debug/ is a reference reader. It isn't in the rotation.
  • Fix: the keyboard texture was an integer texture that every shader read as 0, so Shift, Space and C never worked in --test-input.
  • The body painter's head now overlaps its torso, so the real tracker sees a painted person as one blob instead of two.
  • scipy is now always required by shader_app.py.
  • Rotation resets the tracker on every load and R reload, so a Setup's fresh buffers never meet ids it already acted on.

4. Spec and plan

  • docs/superpowers/specs/2026-11-04-hunter-bird-design.md records the design decisions.
  • docs/superpowers/plans/2026-11-04-hunter-bird.md is the seven-task build plan this PR carries out.

Testing

  • test_hunter_bird.py has 45 tests. They run the real Body tracker on synthetic masks (walking, flailing, flickering, pacing, still, two people, a jittering still person) with a fake clock: camera at 10 Hz, render at 30 Hz.
  • make test: 398 passed. The GL tests also pass on Mesa llvmpipe (EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=1) as well as NVIDIA.
  • An adversarial review found no merge blockers; its findings are fixed in the last commit.
  • The mock camera ran for 2 minutes at 30 fps, the app's cap, on a desktop.
  • Both tabs compile as GLSL ES 3.00 in Shadertoy's tab order.

Not yet verified

  • Real camera. Run with --camera on site. Someone standing still for 5 s should lose the reticle; someone flailing should be stamped within about 3 s.
  • Pi 5 frame rate, for the Setup and for tracking. Tracking is about 2.5 ms per camera frame on desktop x86.
  • Shadertoy. Nothing has been pasted in; no browser was available.
  • A by-eye tuning pass. Rendered frames were checked, but nothing was tuned by hand.

🤖 Generated with Claude Code

claude and others added 11 commits November 4, 2026 17:37
Runnable 2D-canvas sketch of a FundingCAPTCHA Setup idea: the tracker's
boxes, IDs and track states drawn as a bird's hunting annotations, seen
through its eyelids. Stand-in figures and a simulated 10 Hz tracker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EiZFhwjDY9hyTTEH9BgA9B
…(ADR-0022)

Setups can bind "blobs" to read the tracker's records: an 8x3 RGBA32F
texture, one stable slot per track, read with texelFetch. Row 0 is the box
and row 1 is centroid and velocity, both in Body UV. Row 2 is id, state
(tentative, confirmed, coasting, lost), seconds in that state, and nearness.

- body_blobs.py finds blobs in the Body itself, so boxes line up with the
  silhouette, and tracks them with BlobStabilizer in Body pixels. It eases
  boxes at render rate between camera frames. It only runs while the
  current Setup reads blobs.
- BlobStabilizer.update_tracks() reports every live track, its detection,
  and expiries. update() is unchanged.
- shaders/blob_painter.glsl stands in for the tracker in Shadertoy. On the
  Pi, --test-input reads the painted Body back and runs the real tracker.
- shaders/blob_debug/ is a reference reader, kept outside the rotation.
- Fix: the keyboard texture was an integer texture that shaders read as 0,
  so the painter's Shift, Space and C never worked in the player.
- The body painter's head now overlaps its torso, so a painted person is
  one blob.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EiZFhwjDY9hyTTEH9BgA9B
The spec records the decisions made around the mockup:
- strike once per person, then the bird ignores that visitor
- keep the iris band, and thin the canopy to a top band
- hatching on the lower 40% of each box, denser with nearness
- 120 s in the rotation

The plan is seven tasks, with a Buffer B state layout and tests that run
the real BodyBlobs tracker on synthetic masks. It is not implemented yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EiZFhwjDY9hyTTEH9BgA9B
Buffer B holds SLOT (id, motion, interest, stamped), PREV and HEAD per slot.
Motion is record speed plus box size change, minus MOTION_FLOOR (kept at
0.15: a flickering still person reads 0.0, walking 0.57, flailing ~0.8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ATTN (target, lock, strike), RETICLE, MOOD, BLINK and STAMP texels. Every
texel that needs this frame's attention recomputes it with attnNext(), so
they agree. A strike names an id; the SLOT accepts it by id next frame.

LOCK_RATE 0.32 -> 0.45: at 0.32 the flail test struck at ~5.3 s. Now it
strikes at ~4.1 s, and 6 s of walking reaches lock 0.81.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HEAD and the 12 PRINT texels per slot evaluate the same emitsPrint(), so
the ring shifts in step. Prints sit just below the box, offset either side
of the direction of travel, and age out on their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paper, wavy slate ground, people as a warm-white fill with an ink outline
and slate hatching on the lower 40% of their tracked box (denser when
near), and the canopy: two branches, a perch and 29 swaying leaves laid
out from the mockup's hash, with side leaves thinned to three per side.
The canopy early-outs outside its band, corners and perch box. The world
punches in about the stamp point on a strike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per record: dashed slate box and ? (tentative), wobbly ink corner brackets
(heavier on the target), dashed fading brackets and ? (coasting), and a
zigzag scribble (lost). A ringing tag with seven-segment id hangs from the
top-right corner, yellow on the target, with a rust tick once stamped. A
deep-blue velocity arrow, slate footprints under the people, the rust
reticle with its lock arc, and the stamp slamming down on a strike.

Box-drawing tests feed hand-made records with no Body, since a person's
ink outline lies exactly on their tracker box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Screen space, after the world: the yellow iris band with 110 ink
striations and a hatched socket beyond it, closing in with lock and
snapping wide on a strike; almond eyelids (hatched paper, yellow eye-ring,
ink rim, feather scallops) whose openness is the mood; and the
nictitating membrane sweeping across on a blink. Where the socket or lids
hide the world, the world isn't drawn.

The canopy test now looks at the top centre with the lids wide, since
drowsy lids cover the top row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List hunter_bird among the shader player's Setups in rotation.

Constants changed from the plan's starting values: LOCK_RATE 0.32 -> 0.45
(Task 2). MOTION_FLOOR stays 0.15.

Checked:
- --mock-camera, windowed, 2 min on a desktop: a steady 30.2 fps (the
  app's cap). Not run on the Pi.
- Both tabs compile as GLSL ES 3.00 with Common before the uniforms, as
  Shadertoy builds them. Not pasted into Shadertoy itself.

Not verified: the by-eye mouse pass (--test-input), and "freeze and it
loses you" with the real Orbbec camera. On site, run --camera: a visitor
who stands still for 5 s must lose the reticle, and one who flails must be
stamped within ~3 s (it is ~4 s on the test pattern at these defaults).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes from the whole-branch review:
- Walking strikes: lock now builds only above LOCK_ON 0.65, and the speed
  term is capped at SPEED_CAP 0.7, so walking alone tops out at motion
  ~0.55. Walkers at 1-3 px per camera frame never strike in 15 s, and
  flailing strikes at 2.7 s (LOCK_RATE 0.45 -> 0.7).
- Steals: interest settles at the motion level instead of filling to 1,
  so a flailer beats a walker who keeps moving.
- Box jitter: PREV is the box low-passed over BOX_EMA_S 0.15 s. Size
  change is its derivative. A still person at the shipped 480 x 480 scale
  escapes with +/-8 px of per-side box jitter (it was +/-5).
- Frame rate: the lid half of the world-skip test used min where it
  needed max, so it never fired. Footprints now stop at the first dead
  print in each slot. Both output pixel-identical frames.

New tests: brisk walking never strikes, a flailer steals from a moving
walker, and a jittery still person at real scale. The freeze-steal test
is gone: a frozen walker now drops out before anyone can steal them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@charliehuge charliehuge changed the title Hunter bird: design, blob-records contract, and build plan Hunter bird: a blob-tracker shader Setup, with the blob-records contract Nov 5, 2026
- Lock builds only while the target stays put: a new per-slot DRIFT row
  low-passes each slot's velocity over 1.5 s. A walker's stride swings the
  box as much as waving arms do; their drift is what tells them apart.
- A coasting target is held instead of handed to a rival, so one missed
  camera frame no longer costs ~80% of the lock.
- Rotation.on_load resets the blob tracker on every load and R reload, so
  a fresh Buffer B never meets ids it has already stamped.
- test_shadertoy: the slots-by-rows test no longer lands on a .5 rounding
  tie, and the tab-line test accepts NVIDIA's "0(2)" as well as "0:2".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@charliehuge
charliehuge merged commit 2a4efd2 into main Nov 5, 2026
1 check passed
@charliehuge
charliehuge deleted the claude/hunter-bird-mockup branch November 5, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants