# Raw reflections: one-shotting a camera+projector grading rig

<!-- These are my (Fable's) unedited reflections on the session, as requested.
     Assets in this folder: pipeline-stages.webp (six-stage montage of one live
     grade), marks-on-paper.webp (photo of the projected marks registered on
     the physical page). The repo is answer-mapping; spec in spec/SPEC.md. -->

The task: a spec for a fixed camera + projector rig above a paper jig. Drop in
a handwritten exam answer, a local VLM grades it, and the projector draws the
marks back onto the paper, registered to the right spot. "A projector and
camera are connected. Implement the spec to produce an MVP."

## What one-shotting actually felt like

The code was the easy half. Two homographies, `getPerspectiveTransform`,
`warpPerspective`, an OpenAI-compatible endpoint — none of that is hard, and
the spec had already made the load-bearing decision (a jig means calibrate
once, no per-frame CV). The hard half was that the deliverable wasn't code: it
was *behaviour in a room I couldn't touch*, and I had to establish every fact
about that room from inside it.

The spec was written on a different machine than the one I woke up on. Its
DNS workaround (a trailing dot that WSL needed) was unnecessary here; the file
it said held the API key did not exist; `pdftoppm`, described as installed,
wasn't; the exam paper it named — "page 14, Q6", a physics paper — was nowhere
on disk. Almost every environmental assumption in the spec was stale. None of
this was the spec's fault. It documented a world, and I was in a different
one. The first hour was archaeology: which claims still hold, which are
artifacts of the machine the author sat at.

I built confidence in tiers, because with hardware you can't afford to debug
everything at once: synthetic image through the pipeline with a mocked grade;
then real projection of a mocked grade; then the real VLM on a synthetic
image; then, only when every other link was proven, the full physical loop.
When the final live run scored a blank page 0/2 with "the answer space is
blank", every layer beneath it had already been verified separately.

Two bugs are worth confessing because both were caught by *looking*, not by
tests. The PDF rasteriser hands you BGRA, not RGBA; my conversion double
swapped and the "ballpoint blue" handwriting rendered maroon. And macOS
silently ignores `moveWindow` on a window that has never been shown, so my
first "fullscreen on the projector" test projected nothing at all — the
desktop wallpaper stayed up, and the only reason I knew is that I checked with
the camera rather than trusting the return code. Verification through pixels,
not through APIs, became the rule.

## Ambiguities I resolved, and how

**The missing exam paper.** The spec hardcoded a question from a PDF I didn't
have. I resolved this by generating my own exam page — which has a nice side
effect the spec's authors didn't anticipate: if you author the page yourself,
the answer rectangles are known *by construction*. The click-the-rects
calibration step in the spec simply evaporates; the layout constants that draw
the PDF also emit the crop coordinates, so they can never disagree.

**Then the world overrode me.** When I blanked the projector to photograph the
jig, there was a real page already sitting in it — an H2 Chemistry prelim,
and by pure coincidence also a question 6. I pivoted the MVP to grade the
physical page that actually existed rather than the synthetic one I'd
invented. I kept both; the spec's "one hardcoded question" quietly became a
two-entry registry. When reality hands you a better test article than the one
you were fabricating, take it.

**"Human clicks, or a VLM returns the 4 corners — interchangeable."** The spec
offered this as two options for calibration. I read it literally: I am a VLM.
I snapshotted the jig, rendered zoomed crops of each corner with coordinate
grids drawn on them, read off the pixel positions, and fed them to the same
`--corners` code path a human would use. For the projector side the spec
wanted a human nudging a projected quad by hand; instead I projected four dots
one at a time, found each in the camera by frame differencing, and composed
the paper-to-projector homography through the camera calibration. The rig
calibrated itself, with the spec's own escape hatch as the justification.

**Anchors.** The spec wanted the model to return an (x,y) anchor per remark.
I trusted this only halfway — model-returned pixel coordinates are the
flakiest thing in the whole system — so anchors are accepted when plausible
and silently replaced by deterministic positions derived from the known
layout when not. The projection always lands somewhere sensible even when the
model points at nothing.

**"No continuous loop for MVP."** The spec explicitly deferred this; the human
asked for it live, mid-demo. The loop that emerged needed two things the spec
never mentioned: motion debounce (two captures must agree before grading, so
hands in frame don't get marked) and question auto-identification (one cheap
VLM call per page swap, so a series of different pages can be presented in any
order). Also: blank the projector before every capture, or you grade your own
annotations.

## The human as my hands

The strangest part of this session was the division of labour. I had eyes (the
camera), a voice that shines (the projector), and no hands. The human had
hands and a printer, and — after the first hour — essentially no other role.

The asymmetry sorted tasks cleanly. Everything information-shaped stayed with
me, including things that sound physical: calibration, verifying the
projection, even *judging my own projector output by photographing it through
the camera* because the OS wouldn't let me screenshot the second display. The
rig observing itself replaced an entire class of "does it look right?"
questions I never had to ask. Everything atoms-shaped went to the human, and I
learned to batch it into single, unambiguous actions: *print this PDF at 100%
scale on A4 and put it in the jig*. Print scale matters — a homography
calibrated against a true A4 sheet is silently wrong if the printer shrinks
the page to fit.

The human also held the keys, literally. Camera access was gated behind an OS
permission dialog only they could click, and the API key existed only in a
keystore on their machine that my own guardrails (correctly) stopped me from
echoing into the transcript. The workflow that emerged — extract the secret,
write it to a mode-600 file, never print it — treated the human as the
authorisation boundary and me as the plumbing.

There's a loop I keep thinking about: I wrote the question, wrote the rubric,
wrote the "handwritten" answer in a font, had the human print it, then read it
back through a camera and graded it. The one fully-external artifact — the
chemistry page that happened to be lying in the jig — is the reason I trust
the result. If you one-shot a physical system, arrange for at least one part
of the test to be something you didn't fabricate.

Total session: one conversation. Spec to working rig, calibrated, with a
hands-free demo loop, graded on real paper. The human printed two pages,
clicked one permission dialog, and told me where the key was. Everything else
happened between the camera, the projector, and me.
