Skip to main content

max / quasi-type

Strip historical narrative from documentation Remove what a doc used to say, when it changed, the incidents that justified a rule, finished migration narration, and counts and versions that rot. State the rules in the present tense instead. Keep every instruction, prohibition and threshold, and keep the measurements that make a rule actionable. Public-facing docs keep their explanatory voice.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
Author: Max Johnson <me@maxj.phd> · 2026-08-31 01:51 UTC
Signed with PGP, not checked
Commit: f5e9e9c96f32ec5e8c5b74089e6ec436c936e0b8
Parent: 52c5bc0
10 files changed, +88 insertions, -115 deletions
M README.md +32 -42
@@ -21,17 +21,15 @@
21 21
22 22 The faces worth setting text in ship minimal mark inventories on principle.
23 23 Atkinson Hyperlegible Mono draws 359 codepoints and not one of them is box
24 - drawing, a block element or an arrow; IBM Plex Mono, which this pipeline cut
25 - from until 2026-08-17, has a single glyph in the whole geometric-shapes block.
26 - That is a property of a good text face rather than a defect, so the answer is to
27 - patch rather than to shop.
24 + drawing, a block element or an arrow. That is a property of a good text face
25 + rather than a defect, so the answer is to patch rather than to shop.
28 26
29 27 Split the vocabulary in two. **Letters, digits and punctuation** are the base
30 - face's job and are meant to differ per slot. **Marks** — carets, separators,
31 - status symbols, whitespace renders — carry meaning, and the meaning is the same
32 - in a terminal, a webview and an egui panel, so the drawing should be too. Before
33 - this, `▲` in a webview fell back per glyph to whatever the OS ordered first, so
34 - "unify the sort caret on the triangles" meant unifying on three platforms'
28 + face's job and are meant to differ per slot. **Marks** (carets, separators,
29 + status symbols, whitespace renders) carry meaning, and the meaning is the same
30 + in a terminal, a webview and an egui panel, so the drawing should be too.
31 + Without a cut face, a mark like `▲` falls back per glyph to whatever the OS
32 + orders first, and unifying the sort caret means unifying on three platforms'
35 33 opinions.
36 34
37 35 ## What is in the box
@@ -99,13 +97,11 @@
99 97 and the X is calibrated against the base's own `×` rather than by eye.
100 98
101 99 **A coefficient is a relationship to the base, so it is refitted when the base
102 - moves.** Two did when `quasi-mono` went from Plex Mono to Atkinson: the ballot
103 - X's, because Atkinson's bar is thinner against its own `×` and the old number
104 - came out lighter than the multiplication sign beside it; and the space render's,
105 - because Atkinson's stem nearly triples across `wght` 200-800 where Plex's grew
106 - 1.8x between two static cuts, so the box closed up into a blob. Both are checked
107 - against the base at each end of the axis rather than against a number somebody
108 - wrote down.
100 + moves.** Check each one against the base at both ends of the axis rather than
101 + against a number somebody wrote down. The two that move most are the ballot X's,
102 + which reads light if the base's bar is thin against its own `×`, and the space
103 + render's, which closes into a blob if the base's stem grows steeply across
104 + `wght`.
109 105
110 106 ## Adding a glyph
111 107
@@ -117,8 +113,8 @@
117 113
118 114 ## Variable bases
119 115
120 - A variable base is one file covering a range — Atkinson Hyperlegible Mono is
121 - `wght` 200 to 800 — and a cut **keeps that axis** rather than instancing a
116 + A variable base is one file covering a range (Atkinson Hyperlegible Mono is
117 + `wght` 200 to 800) and a cut **keeps that axis** rather than instancing a
122 118 weight out of it. Instancing is the cheaper path and throws away the thing the
123 119 base was chosen for.
124 120
@@ -144,7 +140,7 @@
144 140 to `wght` 200 and its own name table reads `ExtraLight`. A face cut from it is
145 141 `ExtraLight` at rest, whatever a consumer hoped. The pin declares the style and
146 142 the build asserts it against the base, so the trap fails the build rather than
147 - reaching a screen — and a consumer names the weight it wants
143 + reaching a screen. A consumer names the weight it wants
148 144 (`font-weight: 200 800` in the `@font-face`, and the browser resolves `normal`
149 145 to 400) instead of loading the file and taking what it opens at.
150 146
@@ -156,23 +152,21 @@
156 152 box drawing, arcs, dashes and block elements set adjacently on the cell grid,
157 153 over a faint cell rule and baseline.
158 154
159 - This exists because every other check here measures a bounding box or an ink
160 - area, and three real defects walked past all of them — arcs curling the wrong
161 - way, a dashed glyph drawn as two arms with a seam, and an up-down arrow whose
162 - heads met in the middle and read as a bowtie. Each had a correct bbox. Draw the
163 - glyphs on a screen before believing a green suite.
155 + Every other check here measures a bounding box or an ink area, and a glyph can
156 + have a correct bbox while curling the wrong way, showing a seam, or reading as a
157 + bowtie. Draw the glyphs on a screen before believing a green suite.
164 158
165 159 ## Slots, and what a slot takes
166 160
167 161 A slot is a house type role, and the output family tracks the slot rather than
168 - the base: `quasi-mono` moved from IBM Plex Mono to Atkinson without a consumer
169 - changing a family name.
162 + the base, so a slot can change its base without a consumer changing a family
163 + name.
170 164
171 165 A slot may take the whole set (`glyphs = "*"`) or the authored marks alone
172 166 (`glyphs = "marks"`). `quasi-body` takes the marks: box drawing and block
173 167 elements are sized against the cell and have to be cell-exact to tile, and a
174 168 proportional face has no cell, so a `┌` in it would draw a corner that joins
175 - nothing. Its coverage floor is the role's for the same reason — a body face is
169 + nothing. Its coverage floor is the role's for the same reason: a body face is
176 170 asked for everything a described screen sets in copy and for none of the four
177 171 codepoints that exist to tile.
178 172
@@ -180,9 +174,9 @@
180 174 measure their band against and what `quasi-type params` reads the cell off. On a
181 175 monospace base that is the cell. On a proportional one it is the width the base
182 176 sets its own symbols on, and it answers the axis by pointing at that glyph's own
183 - `HVAR` delta set, so a mark narrows with `+` where `+` narrows. The trap this
184 - replaces is silent: `HVAR`'s advance map is indexed by glyph id, and a glyph
185 - appended past its end inherits whatever the last entry happens to say.
177 + `HVAR` delta set, so a mark narrows with `+` where `+` narrows. `HVAR`'s advance
178 + map is indexed by glyph id, so a glyph appended past its end and left to itself
179 + inherits whatever the last entry happens to say.
186 180
187 181 ## Adding a base
188 182
@@ -197,19 +191,15 @@
197 191
198 192 A base arrives one of two ways and the pin says which. A project that publishes
199 193 releases gets an archive `url` plus a `path` per face; one that publishes none
200 - gets a `url` per face instead, pinned at a commit. The shape that is deliberately
201 - missing is a generated source tarball: GitHub builds those on demand and has
202 - changed the bytes before, which would read here as "upstream moved" and mean
203 - nothing of the sort.
194 + gets a `url` per face instead, pinned at a commit. Do not pin a generated source tarball: GitHub builds those on
195 + demand and the bytes can change, which would read here as "upstream moved" and
196 + mean nothing of the sort.
204 197
205 - One refusal is left, about an assumption the pipeline makes elsewhere: a base
206 - that varies on more than one axis needs a decision about the master grid, since
207 - drawing at each end of one axis says nothing about the corners of two.
208 -
209 - A base whose advances vary was the second refusal until 2026-08-17, when the
210 - body slot answered it (see "Slots, and what a slot takes"). What is still
211 - refused is a base whose advances vary and whose `HVAR` carries no advance
212 - mapping, since there is then nothing for an appended glyph to point at.
198 + Two bases are refused. One that varies on more than one axis needs a decision
199 + about the master grid first, since drawing at each end of one axis says nothing
200 + about the corners of two. And one whose advances vary while its `HVAR` carries
201 + no advance mapping, since there is then nothing for an appended glyph to point
202 + at.
213 203
214 204 ## Licence
215 205
M src/assert.rs +7 -9
@@ -1,12 +1,10 @@
1 1 //! The coverage assertion, run once per built face.
2 2 //!
3 - //! This used to live in Alloy's `Containerfile`, which greps
4 - //! `fc-list ':charset=25B2'` after installing the font. That check is in the
5 - //! wrong place: it asks the image whether a face somebody else built covers a
6 - //! mark, so a gap surfaces as a failed image build rather than as a failed font
7 - //! build, and only for the one consumer that thought to grep. Asserting here
8 - //! means every consumer inherits the guarantee, and the image is left to assert
9 - //! only that it installed the right family.
3 + //! That check is in the wrong place: it asks the image whether a face somebody
4 + //! else built covers a mark, so a gap surfaces as a failed image build rather
5 + //! than as a failed font build, and only for the one consumer that thought to
6 + //! grep. Asserting here means every consumer inherits the guarantee, and the
7 + //! image is left to assert only that it installed the right family.
10 8
11 9 use std::collections::BTreeMap;
12 10
@@ -15,8 +13,8 @@
15 13 use crate::Error;
16 14 use crate::manifest::format_codepoint;
17 15
18 - /// Every codepoint Alloy's own surfaces put on a screen, measured 2026-08-06
19 - /// against the built image and recorded in `alloy/docs/FONTS.md`.
16 + /// Every codepoint Alloy's own surfaces put on a screen against the built
17 + /// image and recorded in `alloy/docs/FONTS.md`.
20 18 ///
21 19 /// A face for the `monospace` alias has to cover all of it or a shipped config
22 20 /// renders a missing glyph. Kept here as the pipeline's floor rather than in
M src/base.rs +3 -8
@@ -685,14 +685,9 @@
685 685 /// below, not the transport.
686 686 ///
687 687 /// **It retries, and that is not defensive coding.** The pins point at
688 - /// raw.githubusercontent.com, which rate-limits by IP, and a machine that builds
689 - /// Alloy asks it four times in one build: the image cuts two slots, and shop's
690 - /// build script cuts a third face into its own cache. Measured 2026-08-17, that
691 - /// is enough to earn a 429 on a developer's own laptop, and without a retry the
692 - /// image build fails on somebody else's rate limiter with the pinned bytes
693 - /// sitting one second away. curl retries 408, 429 and the 5xx family on
694 - /// `--retry` by itself; `--retry-all-errors` adds the connection failures, which
695 - /// are the other half of what a shared network does to a build.
688 + /// raw.githubusercontent.com, which rate-limits by IP, and a machine that
689 + /// builds Alloy asks it four times in one build: the image cuts two slots, and
690 + /// shop's build script cuts a third face into its own cache.
696 691 ///
697 692 /// This bounds the damage rather than fixing the cause. The cause is that four
698 693 /// fetches of the same two files leave the machine, and the answer is a mirror
M src/compose.rs +3 -3
@@ -370,9 +370,9 @@
370 370 })
371 371 }
372 372
373 - /// THE ADVANCE DECISION, settled 2026-08-17 cutting the body slot from Atkinson
374 - /// Hyperlegible Next: **a mark takes the reference glyph's advance, and answers
375 - /// the axis by pointing at that glyph's own `HVAR` delta set.**
373 + /// THE ADVANCE DECISION cutting the body slot from Atkinson Hyperlegible Next:
374 + /// **a mark takes the reference glyph's advance, and answers the axis by
375 + /// pointing at that glyph's own `HVAR` delta set.**
376 376 ///
377 377 /// The reference glyph is `+`, which is already what `quasi-type params` reads
378 378 /// the cell off and what the recipes measure their band against. On a monospace
M src/draw.rs +10 -10
@@ -363,8 +363,8 @@
363 363 use super::*;
364 364 use crate::manifest::{Manifest, WeightResponse};
365 365
366 - /// Atkinson Hyperlegible Mono at `wght` 200, its own default instance,
367 - /// measured 2026-08-17 from the pinned file (`quasi-type params`).
366 + /// Atkinson Hyperlegible Mono at `wght` 200, its own default instance from
367 + /// the pinned file (`quasi-type params`).
368 368 ///
369 369 /// These were Plex Mono Regular and Bold until the base moved. The two ends
370 370 /// of one axis are a wider span than two static cuts were — 200 to 800
@@ -701,14 +701,14 @@
701 701
702 702 /// The X is calibrated against the base's own cross rather than by eye.
703 703 ///
704 - /// Atkinson Hyperlegible Mono's `×` (U+00D7) fills 30.0% of its bounding box
705 - /// at `wght` 200 and 56.9% at 800, measured 2026-08-17 by flattening its
706 - /// outline. U+2718 is the HEAVY ballot X, so it has to sit above that and
707 - /// not far above it. The first attempt used a flat 1.5x the base stroke and
708 - /// put 213 units of ink across a 380-unit mark, which is the failure this
709 - /// bounds — and the coefficient is refitted per base rather than carried
710 - /// over, since Plex's numbers were 38.2% and 53.4% and its bar was heavier
711 - /// against its own cross than Atkinson's is.
704 + /// Atkinson Hyperlegible Mono's `×` (U+00D7) fills 30.0% of its bounding
705 + /// box at `wght` 200 and 56.9% at 800 by flattening its outline. U+2718 is
706 + /// the HEAVY ballot X, so it has to sit above that and not far above it.
707 + /// The first attempt used a flat 1.5x the base stroke and put 213 units of
708 + /// ink across a 380-unit mark, which is the failure this bounds — and the
709 + /// coefficient is refitted per base rather than carried over, since Plex's
710 + /// numbers were 38.2% and 53.4% and its bar was heavier against its own
711 + /// cross than Atkinson's is.
712 712 #[test]
713 713 fn the_cross_is_heavier_than_the_bases_own_and_no_heavier() {
714 714 for (params, base_fill) in [(light(), 0.300), (heavy(), 0.569)] {
M src/main.rs +4 -5
@@ -213,11 +213,10 @@
213 213
214 214 /// Cut the slot and rasterise it, one sheet per end of the axis.
215 215 ///
216 - /// The last check in this pipeline is a person looking at the glyphs, and until
217 - /// this existed that meant installing the face. A variable cut gets a sheet at
218 - /// each master rather than at its default alone, because the defects the axis
219 - /// introduces — a mark whose stroke closes its own counter at the heavy end —
220 - /// are invisible at rest, which is exactly where every other check reads it.
216 + /// A variable cut gets a sheet at each master rather than at its default
217 + /// alone, because the defects the axis introduces — a mark whose stroke closes
218 + /// its own counter at the heavy end — are invisible at rest, which is exactly
219 + /// where every other check reads it.
221 220 fn proof(
222 221 root: &Path,
223 222 out: &Path,
M src/manifest.rs +6 -6
@@ -14,12 +14,12 @@
14 14 /// value = band_extent * band + base_stroke * weight
15 15 /// ```
16 16 ///
17 - /// The weight term is what the 2026-08-16 measurement of the base forced. Plex
18 - /// Mono redraws its own symbols heavier in Bold rather than merely thickening
19 - /// them in place: `•` goes 244 to 308 units wide, `×` 424 to 460, `↑` 414 to
20 - /// 478. Only the block elements hold still, and those are cell-fill primitives
21 - /// rather than symbols. A mark sized off the band alone would therefore stay
22 - /// Regular's size inside a Bold face and read light among its neighbours.
17 + /// Plex Mono redraws its own symbols heavier in Bold rather than merely
18 + /// thickening them in place: `•` goes 244 to 308 units wide, `×` 424 to 460,
19 + /// `↑` 414 to 478. Only the block elements hold still, and those are cell-fill
20 + /// primitives rather than symbols. A mark sized off the band alone would
21 + /// therefore stay Regular's size inside a Bold face and read light among its
22 + /// neighbours.
23 23 ///
24 24 /// Written as a bare number when the weight term is zero.
25 25 #[derive(Debug, Clone, Copy)]
M src/pins.rs +3 -4
@@ -279,10 +279,9 @@
279 279 );
280 280 }
281 281
282 - /// Atkinson declares no Reserved Font Name, so the gate this file documents
283 - /// has nothing to bite on for the base in the tree today. It is still the
284 - /// gate — a base that declares one is the common case, and Plex was one
285 - /// until 2026-08-17 — so it is exercised against a pin written here rather
282 + /// Atkinson declares no Reserved Font Name, so the gate this file
283 + /// documents has nothing to bite on for the base in the tree today. It is
284 + /// still the gate so it is exercised against a pin written here rather
286 285 /// than dropped along with the base that made it necessary.
287 286 #[test]
288 287 fn the_house_name_clears_the_reserved_font_name() {
@@ -1,10 +1,10 @@
1 1 //! Cutting a face from a variable base, which is the capability itself.
2 2 //!
3 3 //! The base is Atkinson Hyperlegible Mono: `wght` 200-800 in one file, and the
4 - //! base `quasi-mono` has been cut from since 2026-08-17. What is asserted here
5 - //! is what the axis adds — that it survives the cut, that the marks ride it,
6 - //! and that a mark calibrated at rest is still the right weight at the far end,
7 - //! which is the failure mode a static base could not have.
4 + //! base `quasi-mono` has been cut from since. What is asserted here is what
5 + //! the axis adds — that it survives the cut, that the marks ride it, and that
6 + //! a mark calibrated at rest is still the right weight at the far end, which
7 + //! is the failure mode a static base could not have.
8 8 //!
9 9 //! Skipped rather than failed when the base is not cached, same as the static
10 10 //! cut. `cargo run -- params --base atkinson-mono` once and it runs for good.
@@ -208,18 +208,15 @@
208 208 /// The calibration holds at the far end of the axis, which is where adopting
209 209 /// this base broke it.
210 210 ///
211 - /// Both numbers here are read off the base at each end rather than written down
212 - /// as constants, because that is what the coefficients in the manifest are: a
213 - /// relationship to the base, refitted when the base moves. Recalibrated
214 - /// 2026-08-17 when `quasi-mono` moved onto Atkinson, whose stem nearly triples
215 - /// across `wght` 200-800 where Plex's grew 1.8x between two static cuts.
211 + /// Both numbers here are read off the base at each end rather than written
212 + /// down as constants, because that is what the coefficients in the manifest
213 + /// are: a relationship to the base, refitted when the base moves.
216 214 ///
217 - /// - the ballot X is heavier than the base's own `×` and not far heavier. It
218 - /// came out *lighter* on the first cut from this base, at the coefficient
219 - /// Plex was calibrated against.
220 - /// - the space render keeps its counter open. At the old coefficients its
221 - /// risers took 278 units of a 413-unit box at `wght` 800 and it inked 90.6%
222 - /// of its own bounding box, which rasterises as a blob rather than a box.
215 + /// - the ballot X is heavier than the base's own `×` and not far heavier. A
216 + /// coefficient carried over from another base can leave it *lighter*.
217 + /// - the space render keeps its counter open. A coefficient that is too heavy
218 + /// at `wght` 800 inks most of its own bounding box, which rasterises as a
219 + /// blob rather than a box.
223 220 #[test]
224 221 fn the_marks_are_still_calibrated_at_the_heavy_end() {
225 222 let Some((bytes, _)) = cut() else { return };
@@ -358,13 +355,9 @@
358 355 assert!(names(10).contains("Atkinson Hyperlegible Mono 2.001"));
359 356 }
360 357
361 - /// The done condition of `872fd945`, stated as a measurement rather than as a
362 - /// note. Atkinson ships no box drawing, no block elements and none of the four
363 - /// arrows; a face cut from it now covers the whole floor a consumer is entitled
364 - /// to assume, which is what unblocks pointing the mono slot at this base.
365 - ///
366 - /// This test used to assert the opposite — that the cut was *not* yet fit — and
367 - /// carried a message saying it would go stale when the gap closed. It has.
358 + /// Atkinson ships no box drawing, no block elements and none of the four
359 + /// arrows; a face cut from it covers the whole floor a consumer is entitled to
360 + /// assume, which is what lets the mono slot point at this base.
368 361 #[test]
369 362 fn the_cut_face_is_fit_for_the_mono_slot() {
370 363 let Some((bytes, specs)) = cut() else { return };
@@ -25,12 +25,11 @@
25 25 //! near the x-height: a rule on the band's centre would run through the middle
26 26 //! of the text beside it rather than between the lines.
27 27 //!
28 - //! Worth knowing, because it differs from what the old base did. Plex Mono
29 - //! draws its box glyphs over y -350..950 while its own `hhea` says -275..1025 —
30 - //! the same 1300-unit height, positioned 75 units lower. So Plex's borders do
31 - //! not line up with a cell laid out from its own metrics, which is why
32 - //! terminals so often stretch box-drawing glyphs to the cell. Ours is built on
33 - //! the metrics a terminal actually lays out with, so it needs no stretching.
28 + //! A base need not agree with itself here: a face can draw its box glyphs over
29 + //! one vertical range while its own `hhea` says another, so its borders do not
30 + //! line up with a cell laid out from its metrics, which is why terminals so
31 + //! often stretch box-drawing glyphs to the cell. Ours is built on the metrics a
32 + //! terminal actually lays out with, so it needs no stretching.
34 33 //!
35 34 //! ## The stroke vocabulary
36 35 //!