Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/book-plans/handwired-labs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Handwired Labs — book plan

Status: **plan only, paused 2026-10-20.** No scenes, narration, cover, sources file or blog are built yet.
The caption fact-check is done (`review.md`); building is paused while the Handwired screencast workshop is made.

The first book in a planned Handwired series. It explains each of the 15 experiments in
`lab/` of [chenglou/handwired](https://github.com/chenglou/handwired) at commit
`4ea7fcb27ebe93acf49b9f29a93e162b975bad46`. The `lab/types/` helper is not a lab and is
mentioned only in passing.

## Contents

- `plan.md` — the plan for the whole book: five chapters covering all 15 labs, each with a
visual metaphor, a beat sheet and caption lines that cite `lab/<name>/<file>:<lines>`.
Also includes a "claims cut" list.
- `digests/` — one working digest per lab, plus `framing.md` (overall question, per-lab status,
the 2026-09-03 reassessment). Written from the repo at the pinned commit.
- `planning-assignment.md` — the exact brief given to the planner, so the plan can be re-run.
- `review.md` — Claude Haiku 5.5 fact-check of all 61 captions against the pinned clone (50 OK, 11 FIX with
corrected wording and verbatim quotes). Apply its FIX rows before authoring scenes.

## How the plan was made

- Planning: `claude-sonnet-5-5`, run once through the Claude Code CLI. The result records that
model as the only one used.
- Digests: written by Claude subagents from the cloned repo. They are working notes, not
reviewed research. Check any claim against its `Sources` pointers before narrating it.
- Routing: the book follows the all-Claude routing requested for this book (Sonnet plans,
Haiku research and review, Opus for visuals). **Repo config still says Astra for planning**
(`generator/authoring-models.json`); that is not yet updated.

## To pick this up

1. ~~Haiku 5.5 review pass on `plan.md`~~ done: apply the FIX rows in `review.md` to the captions.
2. Opus 5.5 authors one scene per chapter, `apps/bookshelf/src/viz/books/handwired-labs/chapter-<n>.tsx`,
following the `viz-scene` skill.
3. Narrate each chapter with ElevenLabs; cover is title-only unless OpenAI image credits are available.
4. `npm run book:sources`, the companion blog, `verify-book.mjs`, and the self-graded rubric
in `docs/book-quality.md`.
5. Only after that, plan the rest of the Handwired series.
20 changes: 20 additions & 0 deletions docs/book-plans/handwired-labs/digests/candidate-gallery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# candidate-gallery

**Question:** Can one numeric program build three complete photo layouts at once, score each against the current viewport, and show only the winner, with clicks and focus handled inside the same circuit?

**Mechanism:** `lab/candidate-gallery/candidate-gallery.ts` `createCandidateGalleryCircuit` builds all three layouts for 12 photos every evaluation: Cinema (hero plus a 2-column thumbnail rail), Contact (4x3 contact sheet, no hero) and Story (hero above 3-column thumbnails). Each size is multiplied by a "quality scale" (the reciprocal of a target size, e.g. `1 / 240` for a contact cell, set in `createCandidateGalleryMachine`) and clamped to 1 by `clampQuality`; a layout's score is the `bottleneck`, i.e. the minimum of its clamped qualities, built with `emitMinimum`. `emitArgmax(circuit, scores, unit)` returns a one-hot `winner.selected`; `selectValue` multiplies each candidate's geometry by its bit and sums, so every thumbnail and hero rectangle reads the winning layout. A click goes through `emitRectangleHit` per thumbnail and `emitFirstActive`, and the focused photo is 12 one-hot source cells (`focusField`) that are written back as `nextSource`.

**Result:** Tests pin the choices: a 1400x680 stage picks Cinema, 984x730 picks Contact, 610x830 picks Story (`candidate-gallery.test.ts:31-41`). The circuit has exactly 14 boolean atoms as its rounding points (`comparison.test.ts:12`), and 26 layout selection reads lower to gather reads in one one-hot group (`gather-lowering.test.ts:54-69`). lab/status.md adds that sparse weights drop "from 940 to 848" with gather lowering.

**Status:** Working demo, moved off the main line: it "moved here from the repo root on September 17, 2026 ... since the repo centers on HandwiredOS." The page is titled "Dynamic UI". runtime-cost uses it as a benchmark subject. No open/abandoned verdict beyond that.

**Visual idea:** A browser frame being dragged narrower; beside it three miniature layouts (Cinema, Contact, Story) always drawn, each with a bar made of its quality gauges where the shortest gauge sets the score. As width shrinks, the bars cross, the argmax's one-hot bit jumps from Cinema to Contact to Story, and the 12 thumbnails fly from their slots in the old layout to the new one.

**Sources:**
- lab/candidate-gallery/README.md:1-9
- lab/candidate-gallery/candidate-gallery.ts:1-160, 300-452
- lab/candidate-gallery/candidate-gallery.test.ts:31-100 (test names and expectations)
- lab/candidate-gallery/comparison.test.ts:1-15
- lab/candidate-gallery/gather-lowering.test.ts:54-69
- lab/status.md:228
- commit 4ea7fcb27ebe93acf49b9f29a93e162b975bad46
19 changes: 19 additions & 0 deletions docs/book-plans/handwired-labs/digests/cascade-argmax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# cascade-argmax

**Question:** Can argmax be made exact for every pair of distinct FP32 scores using only ReLU, power-of-two scaling and clamping, and can such a selector still be trained?

**Mechanism:** `lab/cascade-argmax/cascade-argmax.ts` `emitCascadeGreaterBit` computes `relu(right - left)` and then seven stages of "multiply by 2^23, then clamp to [0, 1]" (`emitClampZeroOne`, i.e. `v - relu(v - 1)`). The total gain 2^161 pushes even the smallest positive FP32 difference (2^-149) to exactly 1, while a tie stays exactly 0, so the bit is exact and left wins ties. Each stage goes through `circuit.roundAffine` (`frozenStep`) so the simplifier cannot merge the stages. `emitCascadeArgmax` arranges these bits in a pairwise tree, carries the winner's score up with a two-product select, and `routeCascade` turns the tree into a one-hot. For training, `softCascadeOptions` shrinks the gain so each clamp becomes a ramp centered on a tie; `lab/cascade-argmax/training.ts` `trainSelection` fits a 16-case x 8-candidate score table through the soft ramps, anneals the gain (2^3 up to 2^21), then checks the hard circuit's winners.

**Result:** From the README and `report.ts`: seven-stage soft cascade, seeds 1-10, 160/160 correct hard winners; single clipped ramp, seeds 101-110, 160/160, with "less than half as many operations" in the soft tree. Exact cascade cost vs production `emitArgmax`: 8 candidates is 196 vs 56 operations and 75 vs 15 layers; 64 candidates is 1,764 vs 504 operations and 150 vs 33 layers.

**Status:** Construction works, but the learning claim is narrow: "This is fitting a 128-cell table. There is no learned shared query/key function". The README says "The hard cascade does not provide useful score gradients", and status.md says "Hard score gradients still provide no useful learning signal." Superseded for cost: since September 14, 2026 core `emitCompare` builds an 8-candidate exact tree in 150 operations and 57 layers, or 52 in 18 on a 1/64 grid.

**Visual idea:** Two score bars almost the same height feed a tiny difference into a vertical pipe of seven magnifier stages. At each stage the sliver is doubled 23 times and hits a ceiling at 1. A difference of 2^-149 climbs to a full "1" by the top, while an exact tie stays a flat 0. Then shrink the magnifiers and the step softens into a ramp that gradients can slide along.

**Sources:**
- lab/cascade-argmax/README.md:1-37
- lab/cascade-argmax/cascade-argmax.ts:1-152, 186-245 (helpers outline)
- lab/cascade-argmax/training.ts:34-130
- lab/cascade-argmax/report.ts:23-71
- lab/status.md:202; lab/reassessment-2026-09-03.md:120,126
- commit 4ea7fcb27ebe93acf49b9f29a93e162b975bad46
17 changes: 17 additions & 0 deletions docs/book-plans/handwired-labs/digests/data-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# data-adapter

**Question:** Given a few examples of rectangle rows and desired answers, can an exhaustive search over a tiny vocabulary of exact circuit programs recover reusable lookups (find a row by ID, read a field, do arithmetic) that keep working on new data?

**Mechanism:** `lab/data-adapter/experiment.ts`. One circuit has `CAPACITY = 12` storage slots, each holding an active bit, an integer ID, and left/top/width/height. `enumeratePrograms` lists every program: a predicate subset (active, same-id, first-slot; zero to two), one field read, and zero to two steps (add width, add height, subtract origin X/Y, multiply by scale), giving `7 * 4 * (1 + 5 + 25) = 868`. `compileLookup` turns predicates into one bit per slot; same-id is `ReLU(1 - abs(id - selectedID))`, exact for integers. A read sums field times bit, and `found` counts matches clamped to 1. `buildAdapter` adds the steps with `roundAffine`/`multiply`. `synthesize` runs each candidate through the real `Circuit` on the training scenes and drops it at the first example that rules out every task. The targets come from `oracle`, a separate plain-JS `find`. `repeatedLookup` then pulls out the shared predicate prefix as one reusable `selectedField(field)`.

**Result:** "12 training scenes identify one program for each of eight tasks from 868 candidates", and all eight "agree exactly with an independent JavaScript lookup on 438 held-out scenes". The common lookup recovered is "active AND same ID". Search evaluates 997 candidate/scene pairs in about 60 ms. Verification is 14,016 scalar comparisons. Joint circuits are 200 constructed operations, versus 1,118 for eight separate circuits (README:5, 46, 62-70).

**Status:** Succeeded within its scope, but it is not learning: "Nothing trains neural weights here." The extraction rule "is authored, not a learned neural abstraction". status.md lists it as "No neural training ... Capacity is 12." It is meant as the baseline for the next experiment (decision 8: retrieving a rectangle by ID with soft attention), which is still open.

**Visual idea:** A table of 12 slots, shuffled. A grid of 868 candidate program cards streams past the table. Each card lights rows with its predicate bits, reads a column, applies steps, and is struck out the moment a training scene disagrees. Eight cards survive, all sharing the same "active AND same ID" stamp, which then lifts off and becomes one shared function box.

**Sources:**
- lab/data-adapter/README.md:1-91
- lab/data-adapter/experiment.ts:1-20, 81-227
- lab/status.md:187, 199, 244
- commit 4ea7fcb27ebe93acf49b9f29a93e162b975bad46
19 changes: 19 additions & 0 deletions docs/book-plans/handwired-labs/digests/event-window.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# event-window

**Question:** Can multi-frame window behavior (drag with pointer ownership, focus, click-versus-drag, a maximize toggle) be a pure feedforward function of the current input plus a small bank of latched events, trained without backpropagation through time and hardened to bit-exact agreement with plain TypeScript?

**Mechanism:** `oracle.ts` (`oracleTick`) is the plain-TS ground truth: four rectangles in a 1280x800 viewport, a 4 px slop, a 30 px title bar, and a 2x2 relation table (title = drag, body = click-toggle). `circuit.ts` (`buildEventWindowCircuit`) compiles the same behavior into 502 ops, depth 38, 6 boolean atoms. State is a "latch bank": a live press record written only on pointer-down, a drag-begin latch written when a move crosses slop, and stored state (positions, maximized, focus) written only on events. Every threshold is one form, `bit(clamp01((boundary - measure) x sharpness + 0.5))`, rounded in exact mode, a ramp in soft mode. `train.ts` (`harvestTrainingPairs`, `trainBehaviorParameters`) trains six cells (slop boundary, title boundary, four relation cells) with Adam on single-frame pairs supervising 21 decision bits, annealing sharpness 1/32 -> 0.25 -> 1 -> 4. `hardenParameterValues` rounds relations; `verifyHardenedParameters` runs closed loop against the oracle (`harness.ts` `runClosedLoop`).

**Result:** Closed loop: 0 divergences over 120,000 frames (12 seeds x 10,000), all 16 relation-table settings, 200 sequential drags (README table, `exactness.test.ts`). Training sweep: "20/20 seeds recovered", every slop in (4, 5] and title in [29, 30), 0/30000 divergences each (`training-sweep.ts`). Fixed-ramp training fails: from the true values it drifts to ~30.01 / ~3.97 and diverges 9,120 times in 10,000 frames. `boundary-probe.test.ts` proves no window of K raw inputs (K up to 256) suffices; a latched aggregate does. Cost grows +36 ops per extra press record, depth constant (`measure-cost.ts`).

**Status:** Succeeded within limits. lab/status.md: training "supplies correct internal state and decision labels. Persistent state and temporal dependencies remain." README: "Training recovers parameter cells, not structure." Next steps (double-click from archive record 1) are open.

**Visual idea:** A title-boundary line sliding along a window's y axis while a translucent sigmoid band around it narrows in four snaps (32 px, 4 px, 1 px, a quarter pixel); red/green dots for title/body presses straddle it, and the line settles inside the [29, 30) band, next to a second run where the band stays fixed and the line drifts out to 30.01.

**Sources:**
- lab/event-window/README.md:1-197
- lab/event-window/circuit.ts:136-230
- lab/event-window/train.ts:130-175
- lab/event-window (function list via grep: oracle.ts:101-294, harness.ts:21-112, train.ts:56-315)
- lab/status.md:200, 289
- commit 4ea7fcb
52 changes: 52 additions & 0 deletions docs/book-plans/handwired-labs/digests/framing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Framing digest: lab/ status documents (chenglou/handwired @ 4ea7fcb)

## What the labs are for

The labs test UI behavior as a fixed FP32 numeric circuit (weighted sums, ReGLU gates, boolean atoms). The reassessment names the question: "what making small pieces of behavior editable and learnable enables". Its bottom line: "Keep the smaller numeric representation and the ambition to learn useful computation." The labs are learning experiments plus runtime studies, and since September some also time ordinary JS tasks on CPU and WebGPU.

## Per-lab status table

| Lab | Status as stated in these docs | Key quote (short, exact) | Date of last update if given |
| --- | --- | --- | --- |
| learned-interpreter | Hard-loss optimizer corrected via loss scale | "coordinate 60/60; two adds 59/60 on integers, 15/60 with fractional FP32 checks" | Reassessment Sept 3, 2026 |
| reusable-layout | Built (reassessment's main proposal); transfers to new counts/widths | "learned responsive sizing within a fixed flow layout, not generation of arbitrary page structures" | not given |
| user-effort | Built; simulator-only | "No human observations, error prediction, or usability claim." | not given |
| data-adapter | Built; exact search baseline | "No neural training." | not given |
| event-window | July round one; compression bug fixed | "Training supplies correct internal state and decision labels." | July 2026 (round one) |
| generative-fields | July round one; claims weakened | "valid samples do not establish a smooth valid interpolation" | July 2026 (round one) |
| cascade-argmax | Soft training then hardening | "This fits a table, not a shared query/key rule." | not given |
| trace-compiler | Runtime study; oracle bugs fixed | "Its source interpreter is independently checked against native JavaScript" | not given |
| runtime-cost | Runtime study, CPU only | "compare it with the evaluators that return full arrays, on the CPU" | not given |
| hard-attend | Comparison page | "compares one-hot, direct and softmax gathers" | moved into lab/ Sept 16, 2026 |
| candidate-gallery | Comparison page | "a balanced argmax picks one" | moved into lab/ Sept 17, 2026 |
| learned-gallery | Promoted to src/; lab keeps only the measurements | "the network is fitted to one sketch at a time" | Oct 14, 2026 |
| json-search | Measurements only; page and code moved to src/ | "Against JavaScript in 8 workers: still no." | Oct 14, 2026 |
| knuth-plass | CPU + WebGPU timing study | "Uploading line costs computed in JavaScript never beats the typed-array loop." | Sept 17, 2026 |
| ui-exploration | Historical record plus guide | "16 of the 24 prototypes remain" | Sept 12-14, 2026 (trimmed Sept 16) |
| gather-lowering | Landed in core; tests moved to tests/runtime/ | "it does not discover query/key scoring" | Sept 17, 2026 |
| regex-search, json-highlighter | Removed; lessons moved to docs/patterns.md | "Nothing beat a loop over 32-bit words" | Oct 14, 2026 |
| exp-ste-backprop / exp-ste-reveal, exp-behavior-cells-relations | Superseded | "Made unnecessary by three independent routes" | July / Sept 16, 2026 |

## Reassessment

Two main findings. The learned interpreter's 0/20 failure came from loss scale, not architecture: multiplying losses by 1,000,000 gives 20/20, the same as shrinking Adam's epsilon. So "the old 0/20 result was not sufficient evidence to abandon them". And "some correctness checks compare implementations that share the same mistake".

- **Kept:** the numeric representation, the learning ambition, the sign cascade "as an exact forward construction", and the gather optimization (about 1.29x speedup).
- **Reframed:** generative-fields (1,408 of 4,154 repairs hid items), event-window (a demo bug, and recurrence remains), cascade-argmax (no learned query/key rule), gather-lowering ("neither learned query/key scoring"), trace-compiler (its oracle shared the compiler's bugs).
- **Dropped:** no lab was dropped. It retired nine claims, including "A latch is attention with one slot" and "Compiling ordinary source is a dead end". It also retired the shared brief's "take these as given" framing.
- **Proposed:** finish the numerical diagnosis, learn a reusable layout rule, separate predicting users from choosing for them, and build data adapters. These became reusable-layout, user-effort and data-adapter.

## Open threads

1. Do fractional training examples pin down how additions are grouped? Can the method scale without a complete loss table?
2. Can a shared query/key rule be learned? Decision 8 recommends retrieving a rectangle by id with soft attention, then running it with argmax.
3. Getting UI evidence from "an explicit human task, not from interpreting synthetic preference scores as usability".
4. FP32 order sensitivity. `emitArgmax` picks depend on build order for scores 2 to 5 FP32 steps apart. Gradients depend on statement order.
5. Depth and GPU. The recommendation is to measure one per-layer GPU pass first. HandwiredOS took 2.2 ms per evaluation on WebGPU against 81 µs on the CPU.

## Sources

- Overall question: lab/reassessment-2026-09-03.md:7, :186; lab/research-sweep.md:10
- Status table: lab/status.md:196-204, :212, :220, :228, :230, :103, :120, :240, :237, :289-290; lab/research-sweep.md:27-35
- Reassessment: lab/reassessment-2026-09-03.md:9, :25-38, :100, :105-107, :109-128, :130-175
- Open threads: lab/status.md:40, :170-171, :185, :187, :244, :246
Loading
Loading