Can one create a written or spoken communication that is easily accessible by the general public, but not accessible by AI? The answer is most likely "certainly not!" but this project is my quirky attempt at it.
This project is a heavily vibe coded, conceptual companion to an essay I wrote (without AI assistance, you can check the recording!) called "Souls Only". AI is a tremendously powerful and useful tool and this project shouldn't imply some general anti-AI point of view. However, it explores a few quirky possibilities for creating communication that sighted and hearing humans can perceive that throws typical AI agents perceptual curveballs.
It is worth saying plainly that Claude Code wrote most of this: the font build, the audio toolchain, the demos, and much of this README below this section, with me directing it. The irony that a project exploring a way to create human-exclusive communication (sighted and hearing people) was heavily implemented by claude code is not lost on me, and if anything sharpens the point. The AI tools are now good enough to make this kind of thing easy for a person who's never built something similar before. But that is actually the point of this exploration. The distinction between times where AI intermediation is useful and appropriate (helping me, a guy who don't really know font or audio technology prototype an idea), and instances where an author wants their readers/listeners to be other people. AI has tremendous applications, but perhaps there are instances when there is value sought by the writer/speaker and the reader/listener in knowing that AI intermediation is likely not happening.
The ultimate way to guarantee no intermediation is to use modern encryption techniques and to ensure your readers/listeners have the key to decode. But it becomes more challenging when an author wants the communication to be easily perceivable by the human public and challenging or cost prohibitive for AI.
I believe there will be eventual technologies that support communicating human provenance of work, but it seeems impossible, given the current and assumed rate of AI improvement to create something for human general public consumption and block out AI perception. This may be okay. I fully expect the techniques used in this project to be laughably obsolete soon.
We generally want to create content that is accessible to as many people as possible, across languages and abilities and accessibility optimizing benefits AI perception of the communication. Accessibility technology, including tools leveraging AI have benefitted the goal of accessibliity. For that reason, I would't recommend leveraging the fonts included here for website content intended for the general public. What attempts to break AI perception, will certainly do similar to accessibility screen reading tools unsighted users rely on so please consider this project more in the conceptual, experimental, creative realm and far from the practical.
Soul signing off, Bill
SoulsOnly.ttf - A font whose rendered glyphs spell readable text while the stored
character stream (what copy-paste, HTML/PDF extraction, and scrapers see) is
noise. The font is the decoder, applied only at the rendering layer, and the
cipher is driven by an ordinary keyboard with special firmware: you type normal keys, the keyboard
emits the noise stream, and only this font renders it back into words.
SoulsOnly-VF.ttf - Renders unreadable glyphes by default and requires a user to "focus" the font as well as dinstinguish between legible, but non semantic decoys. This is an attempt to combat AI screen shot capture and optical character recognition. Note that anything a human user can do, a sufficiently powerful AI agent can replicate, so this is only throwing curveballs that are mean to be easy for human users to "hit" and harder for AI.
This is a craft and statement project, not a claim of unbreakable security.
See Limitations in font-cipher-brief.md.
The repo ships an interactive page, demo/decoy.html, where
you can drive the whole thing yourself:
Type any message, then turn the dial (the slider, or the D1-D6 buttons that jump to decoy focal points). At most settings the line reads as a real-but-wrong message; the true text appears only at one spot the buttons don't mark. The lower pane shows the stored bytes (what a scraper or LLM sees), which stay noise at every setting. Select and copy the rendered text to confirm the copied bytes never contain the real words.
Build the assets and open it locally:
./.venv/bin/python -m fontbuild.build_keyboard # dist/SoulsOnly.ttf + SoulsOnly-VF.ttf
./.venv/bin/python tools/make_demo_assets.py # demo/cipher_table.js + demo/SoulsOnly-VF.ttf
python -m http.server 8753 --directory demo # then open http://localhost:8753/decoy.htmlA font has two streams people usually conflate: the character stream (stored
bytes) and the glyph stream (what is drawn after cmap and GSUB run). This
project decouples them:
- The font maps each ASCII code carrier in
cmapto a glyph carrying a meaningless half-glyph fragment (so stray, un-ligated text renders as noise, not blanks), then a GSUBligarule collapses each 2-character code into one opaque half-glyph. - The two half-glyphs tile into the real character. Shared classes reuse one
canonical left half so the left image is ambiguous: the lowercase bowl
a c d e g o q, the lowercase stemm n r u, and the uppercase bowlO C G Qeach share a single left half-glyph. Half-glyph names are opaque, so a font-table dump reveals only meaningless half-shapes, never a half-to-character mapping.
Because four ASCII characters collapse into one rendered character, the stored byte count and the rendered glyph count deliberately diverge.
Plain letters typed in the font do NOT decode: letters are themselves carrier glyphs and carry meaningless half-glyph fragments, so pasting ordinary text and applying the font yields noise. Readable words only ever come from the cipher stream, which reinforces that the font is the key, not a normal typeface.
This is the SoulsOnly.ttf font.
The second font SoulsOnly-VF.ttf" is a variable font with a custom REVL axis. Turning the dial
does not simply scatter and unscatter the text; it lands on a series of
focal points, and at each one every glyph snaps into a real character. At
most of them the characters are the wrong ones: the line reads as a
plausible but false lowercase message (a decoy), so anything that scrubs the
axis and runs OCR comes away with non-semantic garble. Between focal points the
glyph vertices travel, so the letterforms smear from one into the next.
The true text appears at exactly one focal point. Crucially, nothing is stored at any focal point within the font file (not the decoys and not the truth). Each one materializes only by interpolation between two flanking garbage masters whose random swings cancel at the focal's center. This is intended to make reverse engineering harder. So:
- every master in the file is noise; dumping them reveals neither a decoy nor the real text,
- the real point is structurally identical to a decoy: you cannot tell which focal is real by inspecting the font, only by knowing its dial value,
- the distortion is symmetric across the whole axis (no telltale extra churn marking where the secret lives).
This is also what makes the font harder to reverse-engineer than an ordinary variable font. The usual shortcut is to read the masters straight out of the file, or to ask the font for a named instance, and recover the design from the stored data. Here there is nothing in the data to recover: no master, no named instance, and no single stored axis value holds readable text. The readable letterforms exist only as transient shapes the renderer interpolates on the fly between two garbage masters, and the position of the real one is a number kept out of the file entirely. That pushes an attacker off the cheap path of inspecting the file and onto the expensive one of driving the renderer across the axis and reading pixels, which is the honest limit described next.
The entire decode and reveal mechanism lives in the font (cmap, GSUB,
half-glyph tiling, and fvar/gvar); a page contributes only the single REVL
axis value via one control. The axis is unnamed and there is no legible named
instance, so the reveal value is not handed to an automated reader for free.
Honest limit (restated from the spec): the REVL value is one bounded number,
so an automated attacker can sweep axis values and OCR every focal point. The
decoys mean that sweep yields several equally-plausible readings with no way to
rank them, but a reader who knows the dial value, or recognizes the real
message, still wins. This layer is the most portable and self-contained reveal,
and the weakest against automated vision. It is a statement device, scoped as
such.
The built fonts are committed in dist/:
dist/SoulsOnly.ttf: the static font. Renders a cipher stream as readable text.dist/SoulsOnly.otf: the same static font with CFF (PostScript) outlines, for tools that prefer OTF.dist/SoulsOnly-VF.ttf: the variable font (family "Souls Only VF") with theREVLdecoy axis. Defaults to noise; most of the dial shows decoys (real but wrong words) and the true text appears only at one hidden point,REVL= 650 (see "The reveal" below). TTF only: the axis lives in TrueType variation data, which is also the most widely supported variable-font format.
Download and double-click to install (Font Book on macOS, right-click →
Install on Windows), or use @font-face on the web. Remember the font only
decodes the cipher stream; ordinary text rendered in Souls Only is noise
by design. Generate a stream with the encoder below or the cipher keyboard
firmware. The fonts are licensed under the OFL 1.1 (see Licensing).
The keyboard half runs on any QMK/VIA board (this build targets a Keychron V1
Max). Its firmware is generated from the same source as the font, so the codes
can never drift: tools/make_qmk_table.py writes demo/qmk/cipher_table.h,
which drops into a QMK keymap next to demo/qmk/keymap_cipher.c. Once flashed,
Right Ctrl toggles cipher mode on and off and recolors the backlight as the
on-state indicator: Souls Only Blue while ciphering, plain white when off.
Off, it is an ordinary keyboard, and space, Enter, Backspace, and the arrows all
behave normally.
The full hardware runbook (toolchain, keymap wiring, entering DFU, recovery) is
in demo/BUILD.md. It is genuinely fiddly the first time, so
the easiest path is to let Claude Code do it: open this repo in Claude Code,
plug the board in, and ask it to flash the cipher firmware. That is exactly how
this release was flashed. Claude regenerated the table, compiled against the
Keychron QMK fork, waited for the board in DFU, and ran dfu-util end to end.
Reflash whenever the cipher changes, since a board flashed for an older build
emits codes the current font no longer decodes.
Souls Only covers the full US-QWERTY printable set: lowercase, uppercase, digits
0123456789, and the standard symbols. Whitespace stays editable in whole
characters: every character is four bytes, so the keyboard deletes and navigates
in units of four (Backspace removes four, the arrows move four), Space emits one
space character, and Return emits a real newline plus three invisible pad bytes
so the stream stays four-aligned.
docs/superpowers/ design specs and implementation plans
cipher/charset.py the charset + half-slot model: single source of truth
cipher/keyboard.py ASCII carrier-code allocation + encode/decode oracle
cipher/decoy.py per-focal substitution mappings (the decoy letters)
cipher/qwerty.py US-QWERTY keycode -> character map
fontbuild/fragments.py half-glyph slicing (skia-pathops)
fontbuild/resample.py uniform outline resampling so any half can morph
fontbuild/features.py GSUB liga compilation from a FEA file
fontbuild/build_keyboard.py build dist/SoulsOnly.ttf (+ the REVL variable font)
fontbuild/decoy_reveal.py build the decoy-focal REVL font (true text not stored)
tools/make_qmk_table.py generate the QMK firmware table from cipher/keyboard
tools/make_demo_assets.py generate the browser demo table + copy the VF
tools/make_keys_preview.py generate dist/keys.html (the REVL slider preview)
demo/ the physical cipher keyboard demo (QMK keymap, runbook, page)
tests/ pytest suite (run via python -m pytest)
base/Jost-Regular.ttf instanced OFL base font (glyph outlines)
audio/ the audio sibling: a phase-scramble reveal of a spoken message (own README)
(dist/ and the generated demo assets are build artifacts and are gitignored.)
python3 -m venv .venv
./.venv/bin/pip install -r requirements.txt
bash scripts/fetch_base_font.sh # if base/Jost-Regular.ttf is missing
./.venv/bin/python -m fontbuild.build_keyboard # dist/SoulsOnly.ttf + SoulsOnly-VF.ttf
./.venv/bin/python tools/make_otf.py # dist/SoulsOnly.otf (CFF outlines)
./.venv/bin/python -m pytest # run the suite
./.venv/bin/python tools/make_keys_preview.py # dist/keys.html (the REVL slider)
# then: python -m http.server 8753 and open dist/keys.html
# regenerate the physical keyboard demo assets:
./.venv/bin/python tools/make_qmk_table.py # demo/qmk/cipher_table.h
./.venv/bin/python tools/make_demo_assets.py # demo/cipher_table.js + demo/SoulsOnly-VF.ttf
# then open demo/index.html (see demo/BUILD.md for the hardware runbook)./.venv/bin/python -c "from cipher import keyboard as k; print(k.encode('hello world'))"
./.venv/bin/python -c "from cipher import keyboard as k; print(k.decode(k.encode('hello world')))"audio/ is a parallel artwork: the same reveal, in sound, read with
your ears instead of your eyes. A spoken message ships already scrambled into
noise and reassembles into an intelligible voice only at a hidden point on the
same 0 to 1000 dial the font's REVL axis uses.
Where the font warps glyph outlines along REVL, the audio phase-scrambles the
recording: an FFT keeps each frequency's amplitude but a seeded mask randomizes
its phase, so the voice is genuinely destroyed rather than buried under added
static. Rotating the phase back by the dial amount reconstructs it, but only where
that amount matches the one baked into the asset offline.
The method is a code-division overlap. Three channels - the spoken message and two nursery-rhyme decoys (Twinkle Twinkle and Old MacDonald) - are each phase-scrambled at their own focal dial value and summed into one clip the length of a single recording. Tuning to a channel's focal rebuilds that voice while the other two stay a wash of noise; the short decoys loop so every channel plays the whole way through. Turning the dial is like a radio: noise everywhere, one voice surfacing as you pass its focal.
That overlap is also what hides the message from machines. A speech-to-text model run at any focal hears the target voice buried under the other two and transcribes only gibberish, so no adversarial trick is needed - the two-voice floor masks the words on its own. The one thing that recovers the message is a human primed by the words on screen, picking the voice they were set to hear out of the murmur. It is the same human-perception gap the font leans on (top-down priming, you hear what you are set to hear), turned into the defense.
The reveal is pushed one step further than the font's. The font's REVL value is a
number in the shipped variable font; the audio's focal value is not in the
shipped code at all; it lives only in the offline build tool, so reading the
source does not hand it over. The honest limits: a single bounded dial can still be
swept, and the masking is not airtight - a larger transcriber, or a patient human
who listens across the dial, can still pull the message out (Whisper-tiny gets only
gibberish, but a few words can leak). It raises the cost of an automated attack and
turns the task back into listening; it is not an unbreakable cipher. A statement
device, scoped as such.
See audio/README.md to run and build it.
I work and build at Convictional, and write essays at Philosophy of Work about how work is changing in the age of AI. This project is a small artifact of that same preoccupation.
Dual-licensed:
- Code (cipher, fontbuild, tools, firmware glue): MIT.
- Font files: glyph outlines come from
Jost, Copyright 2020 The Jost
Project Authors, licensed under the
SIL Open Font License 1.1. The committed
base/Jost-Regular.ttf(instanced) and any builtSoulsOnly*.ttfare derivative Font Software and are distributed under the same OFL 1.1; they are not MIT.



