All work

Project 04

Braillix

The teacher’s blackboard.

Fifty-one hours, built around a single fear: that a blind student is being taught something wrong and nobody in the room can tell.

A dark blackboard interface showing five class-eleven and twelve maths lines including the binomial theorem and Bayes’ theorem, with a rendered six-dot braille cell below and a forty-eight key symbol rail.
A blackboard, not an app. The teacher writes as they would on a board; the cell below shows exactly what the student is feeling.

A blind student learning maths has a problem nobody in the room can see. Mathematical braille is not the alphabet in dots — it is a whole grammar, where one cell means "a number starts here" in one position and "the fraction closes here" in another. If the translation is wrong, the teacher cannot tell, because they do not read braille. And the student cannot tell, because they have nothing to compare it to. The error just sits there being learned.

That is the failure I built everything around: not "be accurate", but "never be confidently wrong". So Braillix checks its own work by reading it backwards. A completely separate engine — sharing no code with the one that produced the dots — reconstructs what the dots say and compares it to what the teacher wrote. And when that reader hits a cell it has no rule for, it does not guess. It returns a third answer: unchecked. This comparison proves nothing. Most systems would have quietly called that a pass.

Then the constraints stacked up. The hardware team did not know yet how many braille cells their device would have, so I made it structurally impossible for any part of the software to assume a number — the build greps its own source and fails if a file hard-codes one. Student work cannot leave the room, so the whole thing runs offline in a browser, including a 76 MB vision model. And a teacher's line can mix three braille codes at once. I built it in a single fifty-one-hour run, including throwing the first version away halfway through.

Why it was hard

Nemeth is not a character substitution — it is a grammar, where the same cell means "numeric indicator" in one position and "fraction close" in another. A wrong dot is invisible to the sighted teacher and to the blind student both, so there is no human in the loop who can catch it. On top of that the hardware width is genuinely unknown, so no layer of the software is permitted to know how many cells exist; and a single line of a question can mix three braille codes at once. I made all of it run offline in a browser on a village-school laptop, including a 76 MB vision model, because student work must never leave the room.

How it actually works

  • It marks its own homework backwards. A completely separate engine looks only at the dots, says what they say, and that gets held against what the teacher actually typed.

    I check the braille by reading it back. A second engine, sharing no code with the one that produced the dots, reconstructs meaning from the dots alone and compares it to what the teacher wrote.

    1 receipt
    • readback.ts536 lines of independent Nemeth reconstruction, plus separate engines for Devanagari and English.
  • The checker is allowed to say “I don’t know”. When it meets a dot pattern it has no rule for, it says so — instead of quietly counting it as a pass.

    That check has three verdicts, not two. When the reader meets a cell it has no rule for it returns "unchecked" — because collapsing that into "agrees" would turn a gap in the checker into a clean bill of health for the braille.

    1 receipt
    • roundtrip.tsagrees | differs | unchecked, with the reasoning in the file.
  • A line that came out of a photograph of a textbook cannot reach a blind student until a human has confirmed it. Not should not — cannot. The software will not build with an unconfirmed line inside it.

    I made a recognised line unrepresentable in the type system without an explicit human confirmation — enforced at compile time, not at runtime.

    2 receipts
    • lesson.ts:21A recognised line requires a literal confirmed: true.
    • lesson.test.ts:88Compile-time tests asserting the unconfirmed variant fails to type-check.
  • The hardware team had not decided how many braille cells the device would have, so the software is forbidden to guess. Before it ships it searches its own code for a hard-coded number — and plants one first, to prove the search actually works.

    The build greps its own source and fails if any file outside two named authorities hard-codes a cell count — and it first proves the guard catches a planted offender, because a guard that can only ever pass is not a guard.

    1 receipt
  • Two braille displays of different sizes, showing the same line: the bigger one blanks its spare cells rather than leaving yesterday’s dots sitting under a student’s fingers. A stale dot is a lie told in braille.

    Mirroring across several displays pads a wider device’s spare cells to blank rather than leaving stale dots — because stale dots would be a lie told in braille — and a mirrored group takes the width of its smallest device, never its largest.

    2 receipts
  • It will not ship half-offline. The build checks that all eleven pieces it needs to work with no internet are present — and then checks the live site is really handing them out.

    The build refuses to ship without its offline assets, and then checks that the live site is actually serving them.

    2 receipts
A panel reading “232 of 232 curriculum lines translate and read back cleanly, across 27 topics, in 6.3 seconds on this machine”, above a list of per-topic results all in green.
The claim is a button. Press it and the whole curriculum is re-translated and re-checked in front of you.
A reference sheet showing all sixty-four cam positions as rendered braille cells, each labelled with its dot numbers and its Nemeth meaning.
All sixty-four positions a cell can hold, each with its dots and its meaning — the kind of reference sheet you only build if you have actually understood the code. Scroll inside the frame.
A screen showing a quadratic formula, the words “what the dots say — matches what you typed”, and a per-cell table listing dots, meaning and cam position.
The three-way check, visible: what was typed, what the dots say when read back independently, and whether they agree. Scroll inside the frame.
The Braillix board showing the equation x squared plus 3x plus 2 equals 0 written by hand, a rendered six-dot braille cell, a reader panel confirming the dots read back as what was written, and a status bar listing speech as degraded while the maths engine, recognition, USB display and offline copy read ready.
The teacher writes; the dots appear; a separate engine reads those dots back and states what they say. Along the bottom every subsystem declares its own condition — speech is degraded here, and it says so instead of quietly pretending.
The Braillix board driving two braille pods at once over Wi-Fi, with a status line reading “2 cells across 2 pods” and a cell counter reading “cells 1–2 of 7”.
Two pods on the same board, over Wi-Fi, showing the same cells — and the software genuinely does not know how many there are. The build greps its own source and fails if any file hard-codes a cell count.
Braillix on a tablet in portrait, showing five lines of class eleven and twelve mathematics — a quadratic, nCr, the binomial theorem, Bayes’ theorem and integration by parts — above a braille cell reading “cell 1 of 15”, a symbol rail, and a status bar reporting 1,153 half-steps with 1,792 saved, or 61% less motor travel.
Five lines of real class-eleven and twelve syllabus, live. The status bar is my favourite part: 1,792 half-steps of motor travel SAVED, 61% less than the naive route. That is a physical braille cell being spared thousands of movements per lesson, which is the difference between hardware that lasts a school year and hardware that does not. Scroll inside the frame.
The same Braillix board running on a phone in landscape, with two equations, a braille cell, the symbol rail and a status bar showing the maths engine, speech, recognition, USB display, display and offline copy all ready.
The identical board on a phone. A teacher in a government school is far more likely to have this in their pocket than a laptop on their desk, so every control is thumb-sized and nothing is hidden behind a menu.

The numbers

232 / 232
curriculum lines translated and read back cleanly, across 27 topics
receipts
  • ACCURACY.mdEvery line listed; zero with anything to report.
  • syllabus.tsThe 232 lines themselves, counted from source.
64.2s → 1.25s
press-to-result, after background model warming
receipts
  • store.ts:843The warming sequence and the load-race fix it required.
727
unit tests, plus 101 browser journeys
receipts
390 / 834 / 1440
pixel widths asserted, each with a no-horizontal-scroll check
receipts
9
Indic scripts covered by one Bharati table
receipts
0
network requests — it is fully offline, including a 76 MB vision model
receipts
  • vite.config.ts:31Hand-written service worker; fonts and both recognisers self-hosted, no CDN is ever contacted.

The part most people would have skipped

One of my tests caught a bug in the expression rather than in the braille. In the sum-of-an-arithmetic-series formula, my segmenter mistook a subscripted term for an English word and left the equals sign stranded at the front of the maths, where it became a fraction numerator. Every braille cell was faithful to the expression it had been handed — the expression was already wrong.

What it can't do

  • Handwritten Devanagari words genuinely do not work — the recogniser is trained on print, and no free browser-runnable model existed when this was built. The app says so rather than guessing.
  • Braille is Grade 1 plus Nemeth; contractions are not implemented.
  • Multi-device mirroring is verified against emulated hardware, not against two physical devices.
  • Screen-reader user testing has not been done. Accessibility here is asserted structurally and by automated test only.
Built with
  • Vite 8
  • React 19
  • TypeScript
  • Zustand
  • temml
  • speech-rule-engine
  • ONNX Runtime in a Web Worker
  • Tesseract
  • ESP32 / Arduino C++
Iron Man standing in a workshop, captioned: Lock in — build yourself.
Braillix was fifty-one hours start to finish, including tearing the whole thing down halfway and rebuilding it. This is on my wall, and it is roughly what that felt like.

The Horsehead, in infrared

Change the wavelength and a shadow becomes a structure.

In visible light the Horsehead is a silhouette — an absence. In infrared the same object turns out to be lit from within, full of detail that was always there. Nothing changed but the instrument.

The Horsehead Nebula in infrared · NASA, ESA and the Hubble Heritage Team (STScI/AURA) · CC BY 4.0