node · 2026-05-13 project

# robot-framework · letters on butcher paper

Working node: where the project IS right now. The public trailer lives at robot-draws. The long historical page is archived in versions/.

Why it exists (Jake, 6/10 memo): "make the loop big enough that you can see it" — an OODA loop blown up in space + time, like the walk-around computers in 80s documentaries. Future knobs: tighter/looser loop · more per action · most efficient observe.

Also from the 6/10 12:28 memo: "the robot can make a documenting page for itself" · battery life is a lesson to measure, not a blocker · "the real constraint is… the marker is always making a mark" (→ pen-lift).

Lived verdict (6/10 4:13 PM memo, from the cabin): "I'm doing the thing that's been on the [docket] for like 10 years… Doing the robot thing. And it's working." · "It's working."

## Phase: ① CALIBRATE (butcher paper is a NEW surface — whiteboard constants don't transfer)

go-time protocol | Jake places the laptop and walks away. Agent runs `letters/04-session-protocol.md` end-to-end: connect → baseline snapshot → start recorder → probe moves → log. Jake sees the green camera light as the heartbeat.
known-good stroke | `turn_right 360` → clean ~25cm circle (whiteboard truth; re-verify on paper).
known trap | Partial turns (90°/180°) are open-loop timed and drift. NOT a left/right bug — that one is fixed. Precision unlock: wire `move(0x23)` into `/command`.
vision model | Setup driven by Fable (this). Loop runs `google/gemini-2.5-flash` via OpenRouter (key: `~/Documents/pi-mono/.env`). Cheap swap candidates: gemini-2.5-flash-lite, claude-haiku-4-5.

New-surface hazards: off-paper is now bare wood floor (marker ink on wood!). Blue-tape edges may snag the caster. Paper corridor is WIDE and SHORT in frame — vertical headroom is small. Books prop the laptop at frame-bottom: keep Dash away from the bottom edge.
The old left/right turn bug is FIXED. Original harness hard-coded positive degrees both ways; current `dash_ble.py:214-232` takes signed degrees, `server.py` negates for turn_left. Solving it on camera = blog beat (see robot-draws).

## NEW (6/11): phone-head — the Pixel taped to Dash's head is a sensor pod

what | Jake's 6/11 memo built same night: `node phone-head/server.mjs` on the Mac, Pixel opens the printed https URL in Chrome, taps START. Zero install, zero npm deps — web build now, native app is the documented upgrade (`phone-head/README.md`).
senses | Rear camera ~1.4 fps + 5 Hz packets: accel/gyro, absolute compass (first ground-truth turn measurement!), magnetometer, light, mic level, geo, battery. No lidar on Pixels; ToF/barometer need the native path.
claude's loop | Observe `curl /state` + `/frame.jpg` → predict → one verb to `:5174/command` → check. Contract: `phone-head/CLAUDE-LOOP.md`. Smoke-tested end-to-end with a fake-phone WS client.

## Roadmap

① calibrate | ✅ FIRST RUN DONE 2026-06-10: the O is on paper (closed 26cm circle ×2, deg/s=43 holds). Stroke vocabulary verified — see `letters/01-stroke-vocabulary.md`. Next unlock: wire `move(0x23)`.
② letters | ← NOW (gated): O + C achievable open-loop; everything else needs closed-loop `move(0x23)` wired into `/command`.
③ THE FONT | AC (2026-06-10): all 26+ letters → photos → SVG → installable .ttf "Dash Sans". Enablers: head-servo pen-lift (`letters/05-pen-lift.md`) + `move(0x23)`. See `letters/06-font-pipeline.md`.
④ simulator | ✅ CALIBRATED 2026-06-11: `letter-sim.html` v2 (real-run defaults) + headless `sim-runs/run-sim.mjs` — 50 seeded runs, 8/9 channels faithful (fidelity section below). Web deploy still open.
⑤ idea (6/10 memo) | Post-calibration deterministic executor: lines → deterministic program, no LLM per stroke — "more happens in the action" — with a preview step (draw → see picture → execute). Seeds in robot-framework.md.

# 📸 Highlights — the knockdown reel

Real footage from the repo. The headline event: 2026-06-10 late night, "knock the blue tape off the shoebox" — Dash drove THROUGH the box at speed 400, 3 strokes, zero turns, box shoved off the paper's edge. Now on its own page (per Jake's 6/13 ask).

→ Open Highlights.html — the ram timelapse video, the knockdown frame-by-frame (①–⑤), rounds 3–6, and the fun drawing images. All real committed media.

One open ask: Jake wanted "a quick video of us really knocking down the robot" — that exact clip does NOT exist in the repo (the knockdown was captured as stills; the only ram-at-speed video is the head-pen timelapse). Tracked as a PROPOSAL on Highlights.html, blocked on Jake's footage. No media fabricated.

# 📱 The Android app — Dash Remote

Yes, it got made. It's "Dash Remote", an Expo / React-Native phone remote that drives Dash via the laptop control server at `:5174` over LAN. Source: `android/App.tsx` + `android/README.md`. Per Jake's 6/13 ask, it now lives on its own page.

→ Open Android.html — the two screens (faithful render from `App.tsx`, no real screenshot exists yet), full API contract, how to run it, and v1 limits.

what it is | Thin phone-side remote. The phone never BLE-talks to Dash directly — Dash allows one BLE peer at a time, and that's the laptop. The app drives over HTTP. Expo managed workflow (ships in hours; bare-native is one `expo prebuild` away).
two screens | Server picker (URL + Connect, AsyncStorage) → Control screen (drive pad, head-tilt cross, Say, status pill + battery, Pair/Drop Dash; polls `/health` every 2s).
full detail | → Android.html. Sibling: `phone-head/` is the opposite direction (sensors UP, not commands DOWN).

▤ html in this node — `robot-framework.html` (this page) · `ooda-speed.html` (loop-speed investigation, absorbed 2026-06-11) · `letter-sim.html` (calibrated simulator v2)
new (6/13): `Highlights.html` (real knockdown footage + the open knockdown-video proposal) · `Android.html` (Dash Remote — render from App.tsx) — both split out to their own pages.