Text and interface

How words, talk boxes and buttons fit a 320×180 frame on a phone, and what it takes for them to stay readable. On every current phone, holding the 180-pixel short side at a whole device-pixel scale makes one art pixel about a third of a millimeter (0.31 to 0.39 mm). A 5×7 pixel font with 2-pixel descenders on a 10-pixel line then has capitals about 2.3 mm tall, near iOS body text, and fits 48 characters across a talk box in monospace or about 65 in a proportional face squeezed from the same glyphs; a 3×5 face is the floor, good for labels and numbers. A fingertip is 21 to 29 art pixels, an eighth to a sixth of the frame’s height, so three stacked choices and a three-line box take two thirds of the frame, and in landscape choices belong side by side. Text over busy art needs a 1-pixel outline in the frame’s darkest ink or a plate behind it. Accented capitals need 2 to 3 more rows of line height, and Chinese, Japanese and Korean need their own bitmap fonts at 10 to 12 pixels. Tycho has no bitmap text at all today, only a DOM typewriter (Typer), and Elsewhere’s talk box is HTML over the canvas. Palettes everyone can read are on Palette and resolution.

Proposed: none of this is in Tycho yet. The demos run here in the page, written in plain JS with the study’s own kit (demos/kit.js), not Tycho; each says how it would run in Tycho: per frame (the GPU stand-in’s calls), pre-bake (painted once offscreen), or shader (per-pixel work every frame). What would move into Tycho, and when, is in the roadmap.

Text marked (inference) is reasoning from the sources, not a sourced fact. Figures marked (arithmetic) are computed from published screen sizes and the demos’ own fonts.

What there is today

Tycho’s only text machinery is in the interface kit, and it works on DOM elements, not pixels (ui.md). Typer reveals the newest lines of a conversation a letter at a time, as Merryfolk’s talk sheet does:

Setting Default What it does
cps 40 Letters a second
stop 260 ms A breath where a word ends a sentence
pause 110 ms A breath where a word ends a clause
first 160 ms A beat before the first spoken line
gap 320 ms A beat between spoken lines
after 200 ms A beat before a line shown whole (your own words, a note)

finish() shows the rest at once (a tap hurries it), and with reduced motion everything shows at once. Each line keeps its room while hidden, so nothing below it moves. There is no glyph, font or fillText code anywhere in src/ beyond the dev panel’s CSS.

Elsewhere’s talk box (js/talk.js, css/elsewhere.css) is one HTML box for every conversation in every world, laid over the canvas. Its lines are 15 px system sans at line-height 1.35, with the speaker’s name in gold; old lines fade to 55%. Its answers are buttons in 14 px text with 8 px by 10 px padding and 6 px gaps, about 35 CSS px tall (arithmetic): above WCAG 2.2’s 24 px minimum, below Apple’s 44 pt. On a keyboard they’re numbered 1 to 9; on touch the numbers hide. The box sits at bottom: max(10px, env(safe-area-inset-bottom)) and moves to the right in landscape.

So the house style’s own rule, eight inks to a frame, stops at the talk box: its words are in the browser’s font, anti-aliased, in colors outside the palette. That may be the right call (it’s sharp, it scales with the system, it handles every script), but it is a choice to make knowingly (inference). The rest of this page is what a pixel-text path would take.

Pixel fonts at 320×180

At 320 pixels across, the useful faces run from 3×5 capitals to about 6×11.

Face Glyph Where it shipped
PICO-8’s own 3×5 in a 4×6 cell; 8 wide for characters 128 and up PICO-8: 32 characters across its 128 pixels (manual)
m5x7 (Daniel Linssen) Average width 5, ascender 7, descender 2, x-height 5; many accented letters; CC0 Countless jam games (itch.io)
m6x11 (Daniel Linssen) 6 by 11 Balatro, the whole interface, often bouncing (itch.io; Fonts In Use)
Press Start 2P (codeman38) 8 by 8, from 1980s Namco arcade lettering; OFL Best at multiples of 8 px (Google Fonts)
8-Bit Operator JVE Pixel sans, tighter in menus, looser in dialogue Undertale (Fonts In Use)

PICO-8’s custom fonts show what a pixel font is as data: 8 bytes a glyph times 256 glyphs, 2,048 bytes, each glyph an 8×8 bitfield, one byte a row, plus a width, a height, draw offsets and a per-character adjustment that can “draw the character one pixel higher (useful for latin accents)” (PICO-8 manual). Community fonts for it go proportional: “mostly 3x5 but occasionally 2x5 and 3x6 with a few descenders”, widths stored in 4 bits a character (Lexaloffle BBS).

The study drew three faces of its own for the demos: a 3×5 face (capitals, figures, punctuation; lowercase prints as capitals), a 5×7 monospace face with lowercase on a 5-pixel x-height and 2-pixel descenders, and a proportional face made from the 5×7 by a rule rather than by hand. The rule is the squeeze: drop the middle column of a glyph wherever it only repeats a neighbor in every row, then trim empty columns. O, A, H, e, s and n go 4 wide; M, W, T and x stay 5; i, l and punctuation shrink to 1 to 3. On the demo’s paragraph the average advance falls from 5.99 to 4.37 pixels, 27% narrower (arithmetic).

Three pixel fonts at 320×180. The same paragraph in a 3×5 capitals face, a 5×7 monospace face with descenders, and a proportional face squeezed from it (with or without automatic kerning), over a deliberately busy seeded ground in the frame's eight inks: bare, with a shadow, with a 1-pixel outline, or on a plate. Width moves the measure; Leading adds rows between lines (accented capitals need about 3). The readout gives characters a line, lines a box, and the capitals' height in millimeters on an iPhone 15. The faces were drawn for the study, not copied.

What fits, at the demo’s own advances (arithmetic):

Face Advance Line Characters in 288 px Lines in a 40 px box Lines in 172 px
3×5 4.0 6 72 6 28
5×7 mono 6.0 10 48 4 17
5×7 proportional 4.4 10 65 4 17
8×8 (Press Start-like) 8 10 36 4 17
12 px CJK, full width 12 to 13 14 22 2 12

288 px is a talk box 304 px wide with 8 px of padding; with a 32-pixel portrait in it, the 5×7 mono line drops to 43 characters, the proportional to 59. Bringhurst’s print measure is 45 to 75 characters; subtitle guidance for games caps a line at 38 to 40 (Game Accessibility Guidelines; 80.lv, Ian Hamilton). In portrait the frame is about 196 pixels wide, so a 5×7 mono line holds about 30 characters, and a proportional one about 41 (arithmetic).

Proportional faces need kerning more than monospace ones do. The demo’s automatic kerning compares the right edge of one glyph with the left edge of the next, row by row and against the rows above and below so that diagonals never touch, and closes the pair up by one pixel wherever at least two clear columns would otherwise sit between them: “To”, “LT”, “r.” (inference). In a BMFont file the result is a list of kerning first second amount lines (AngelCode).

How big is big enough

On a phone, an art pixel at 320×180 is about 0.33 mm. Accessibility guidance for games asks for bigger text than most pixel fonts give.

Guideline Asks for At 320×180
XAG 101, console Body text at least 26 px at 1080p 4.3 art px (at 6 device px each)
XAG 101, mobile 18 px at 100 DPI, 36 at 200, 72 at 400, scaled with DPI 4.6 mm: about 14 art px if that is the em (inference)
Game Accessibility Guidelines, subtitles At least 46 px at 1080p About 8 art px
IGDA GASIG 46 px for subtitles, 32 px for other text 8 and 5 art px
iOS default body text 17 pt; caps about 12 pt ≈ 2.0 mm on an iPhone 15 (arithmetic, caps at 70% of the em assumed) 6 art px of cap height

XAG also asks, for blocks over two lines, for at most 80 characters a line (40 for CJK), line spacing of at least 1.5 times, paragraph spacing of twice that, and text that players can scale to 200% (XAG 101). Game subtitles get 46 px at 1080p, at most 40 characters a line and two lines (three in exceptional cases), on a solid or semi-opaque backing (GAG: size; GAG: presentation; IGDA GASIG).

Read against that table:

At 320×180 on a phone, a 5×7 face with descenders on a 10-pixel line is about the size of iOS body text, and fits about 48 monospace or 65 proportional characters across a talk box. A 3×5 face is for labels and numbers. Players who need larger text need a setting that scales the text, not the world: a second, larger face, or the talk drawn at a finer scale than the art.

A larger-text setting has two honest paths in a pixel game (inference):

Text over busy art

Light text over a busy pixel scene needs a ring of dark around it. At 320×180 the choices are:

Backing What it costs Where it fails
Bare Nothing Over light or busy ground, which is most ground
Shadow (1 px down, or down and right) 1 px of height Over dark ground; counters stay open
Outline (1 px, all 8 neighbors) 2 px of width and height a glyph; outlines of neighboring letters merge, which reads as a band Never, if the outline is the frame’s darkest ink; but small counters (e, a) close up at 3×5
Plate (a box behind the text) A rectangle of the frame Never; it hides the art behind it

The guidelines agree with the table’s last row: a solid or semi-opaque backing, ideally with an outline or shadow (GAG). XAG counts an outline’s pixels toward text size only if their contrast to the background exceeds 2:1 (XAG 101). WCAG’s text contrast is 4.5:1 (3:1 for large text, 18 pt or 14 pt bold), measured on luminance precisely because luminance contrast doesn’t depend on hue, which covers color deficiencies (W3C Understanding 1.4.3).

Under an eight-ink rule the outline must be one of the eight, so the darkest and lightest inks become the text pair, and their ratio is a one-line check per palette. Elsewhere’s four palettes measured in the colorblind demo give 12.8:1 (the frogs’ day), 14.1:1 (Mrs. Pell’s), 13.3:1 (the Road at dusk) and 17.0:1 (the lighthouse lit), all well over 4.5 (arithmetic). The lighthouse’s NIGHT palette is nearly black by design; text over it needs a plate or the lit print.

Accents and other scripts

A 5×7 face with a 5-pixel x-height has two rows above its lowercase for accents (é, ü, ñ) and none above its capitals. The demo decomposes each character with Unicode NFD, so é becomes e plus a combining acute. It then draws the base glyph and a mark from a table of seven, one clear row above the letter’s top. One table covers most Latin accents, and i and j lose their dots under one. Capital marks (É, Ü, Ñ) land 2 to 3 rows above the cap height, in the line above’s descenders, so a face that supports them needs a 12- or 13-pixel line instead of 10 (arithmetic). PICO-8’s per-character “one pixel higher” flag is the same problem solved the other way (PICO-8 manual); m5x7 includes many accented letters at its 9-pixel glyph height (itch.io).

Chinese, Japanese and Korean are another matter:

Font Size Coverage
Misaki 8×8 Japanese, made for the PC-E500 pocket computer: kana and JIS level 1 kanji (2,965 kanji, general knowledge) (Arduboy port)
Fusion Pixel Font 8, 10, 12 px; monospaced and proportional Latin, Simplified and Traditional Chinese, Japanese, Korean builds; 8 px borrows Misaki and Galmuri; OFL (GitHub)
Ark Pixel Font 10, 12 px (16 px deprecated in 2026) Still missing many characters; OFL (GitHub)

Fusion Pixel’s own notes say that in monospaced mode some complex glyphs “cannot be drawn clearly”, and recommend proportional (GitHub). At 12 px plus 2 of leading, the 180-pixel frame holds 12 lines and a talk box’s 288 px holds 22 characters, under half the Latin count, which is exactly the halving XAG writes in (40 characters for CJK against 80) (arithmetic).

What shipped games do:

A game’s own text needs a few hundred to a couple of thousand distinct characters. At 12×12 and 1 bit a pixel, that’s 18 bytes a glyph, about 36 KB for 2,000 glyphs, small enough to pre-bake per language (arithmetic).

Fonts as data

BMFont’s text format is the common interchange for bitmap fonts:

Line Fields
info Face, size, padding, spacing, outline thickness
common lineHeight (between lines), base (top of line to baseline), atlas size, pages, packed
page Id and image file
char id, atlas x y width height, xoffset yoffset (where to draw), xadvance (how far to move), page, chnl
kerning first second amount

That’s from AngelCode’s format page; Celeste and Stardew both load it.

In an engine that keeps pictures as palette indexes, as Elsewhere’s Ink does, text is just more ink. Each set bit of a glyph writes one index; an outline is the same bits dilated by a pixel, written first in the outline’s index; a shadow is the same bits offset. The text then obeys the palette like everything else and reprints with the world: the same words at the frog stall by day and at dusk (inference). The demos’ drawText does exactly that into a Kit.Pix in a few dozen lines. Drawing per frame within Tycho’s GPU stand-in means pre-baking each face once into an atlas canvas per ink pair and drawing glyphs with drawImage, about 60 calls for a line of 60 characters, or caching each finished line as one canvas (inference).

Talk boxes

The talk box’s conventions are old and stable: a framed box at the bottom, a portrait, a name tab, text revealed at a set speed with breaths at punctuation, the first tap completing the line and the second moving on, a small blinking marker when more waits, and choices as a short list. Each has a pixel-specific detail.

A talk box at 320×180. A nine-slice frame (3-pixel corners, edges repeated, the middle filled), a portrait slot and a name tab, text revealed with Tycho's Typer timings (40 letters a second, 260 ms after a sentence, 110 ms after a clause), wrapped in the font's own advances. Tap the frame: the first tap completes the line, the next moves on, and after the second line three choices appear. Their height is Apple's 44 pt or Android's 48 dp for the chosen phone, in this frame's pixels; the dotted circles are a 9 mm fingertip at the same scale. Row puts the choices side by side inside the box.

The pixel-specific details:

Fingers and pixels

Every touch guideline lands between 7 and 10 mm:

Source Minimum target
Apple HIG 44×44 pt (general knowledge; see W3C issue 1831)
Android accessibility 48×48 dp, “about 9 mm, regardless of screen size”, 8 dp apart (Android help)
WCAG 2.2, SC 2.5.8 (AA) 24×24 CSS px, or spaced so a 24 px circle on each touches no other target (W3C)
WCAG 2.2, SC 2.5.5 (AAA) 44×44 CSS px
Parhi, Karlson & Bederson 2006 One-handed thumb: 9.2 mm for single taps, 9.6 mm for sequences; errors stopped falling at 9.6 and 7.7 mm (Microsoft Research)

44 pt is 7.3 mm on an iPhone 15, and 48 dp is 7.6 mm at Android’s nominal 160 dp to the inch, so Google’s “about 9 mm” is generous (arithmetic).

In art pixels at 320×180 (arithmetic):

Phone One art px 44 pt or 48 dp 9 mm fingertip
iPhone 15 0.33 mm 22 px 27 px
iPhone 15 Pro Max 0.39 mm 19 px 23 px
iPhone SE 3 0.31 mm 22 px 29 px
Pixel 8 0.36 mm 21 px 25 px
Galaxy S24 0.37 mm 24 px 25 px

So a fingertip covers 21 to 29 art pixels: as tall as two and a half lines of 5×7 text, an eighth to a sixth of the frame’s height. Some consequences (inference):

What 320×180 becomes on a phone

Integer scaling on a phone is a choice between letterboxing and widening. Elsewhere’s View widens: it fixes the short side at 180 art pixels and fills the rest of the screen, which is what Palette and resolution recommends. The table below holds the short side at a whole number of device pixels (arithmetic; iPhone sizes from Use Your Loaf, Android sizes general knowledge):

Phone CSS px, device ratio, ppi Device px per art px Screen in art px (landscape) 7-px capitals
iPhone 15 393×852, 3, 460 6 426×196 2.3 mm
iPhone 15 Pro Max 430×932, 3, 460 7 399×184 2.7 mm
iPhone SE 3 375×667, 2, 326 4 333×187 2.2 mm
Pixel 8 412×915, 2.625, 428 6 400×180 2.5 mm
Galaxy S24 360×780, 3, 416 6 390×180 2.6 mm

Two findings for Elsewhere follow from this table.

Elsewhere's View picks its scale in CSS pixels, floor(min(innerWidth, innerHeight) / 180). On a phone whose device pixel ratio isn't whole, such as the Pixel 8 at 2.625, that makes an art pixel 2 × 2.625 = 5.25 device pixels, so rows and columns alternate 5 and 6 device pixels: uneven pixels, and text that shimmers as it scrolls. Choosing the scale in device pixels, floor(min(w, h) × devicePixelRatio / 180), then sizing the canvas in CSS pixels as that over the ratio, gives an exact 6 on the Pixel 8. The study's own kit already does this.

The same change helps wherever the browser’s bars eat the short side. Below 360 CSS px of height, View’s k falls from 2 to 1, which on a 3× phone halves the art from 6 device pixels to 3. A device-pixel scale steps down only to 5 (arithmetic, inference).

The safe areas of an iPhone 15 are 59 pt at the top and 34 at the bottom in portrait, and 59 at each side and 21 at the bottom in landscape (Use Your Loaf). In art pixels at 2 pt each, that’s 29.5 pixels at each side and 10.5 at the bottom in landscape. A centered 320×180 frame has 53 spare pixels at each side, clear of the camera island, and 8 above and below, so only its bottom 2 or 3 rows sit under the home indicator (arithmetic). A widened frame’s edges fall under the island, which suits art that runs off every edge (the house style’s full bleed) and doesn’t suit text: talk boxes and choices should be laid out inside env(safe-area-inset-*) converted to art pixels, while the land keeps going under the notch (inference). Safari reports side insets only with viewport-fit=cover (WebKit bug 272779).

Color is a second channel

About 8% of men and 0.4% of women see color differently, nearly all of them on the red-green axis (DaltonLens). Interface conventions that lean on color fail them first: a red “no” and a green “yes”, a highlighted choice told only by hue, a quest marker in a red that a protanope sees as near black (Wikipedia).

The rule for the interface is redundancy (inference):

The demo’s picked choice turns lighter, not only bluer, for that reason. For the palette itself, how to check that eight inks stay distinct under protanopia, deuteranopia and tritanopia, and how to fix them when they don’t, see Palettes everyone can read on the palette page.

What it means for Tycho

Each of these would be an option any game can use, following Tycho’s rules; none changes a default.

Part What it would be How it runs
Tycho.text bitmap fonts A font as 1-bit glyph rows plus metrics (cell, advance, line, baseline), loaded from a small JSON or a BMFont .fnt text file; a mark table for NFD accents Pre-bake: an atlas canvas per face and ink pair
Layout measure, greedy wrap, paging in N lines, optional automatic pixel kerning Plain JS, once per line
Drawing Into an index buffer (glyph bits as one index, outline and shadow as others), or from the atlas with drawImage within the GPU stand-in Per frame
Typer for pixels The same timeline (typeTimeline already gives each step’s time and letters) driving a character count, not DOM spans Per frame
A nine-slice frame Corners, repeated edges in whole periods, a filled middle, from a small sheet Pre-bake per size
Scale in device pixels A view helper choosing the whole device-pixel scale for a short side in art pixels, and the safe-area insets in art pixels Once per resize
Touch sizes in art pixels targetPx(): 44 pt or 48 dp, and a 9 mm fingertip, in the current art pixel Once per resize

The DOM Typer stays as it is for games whose talk is HTML; Elsewhere could keep its HTML box and still take the device-pixel scale (inference). The fonts in the demos are the study’s own and could seed Tycho’s default faces. Accessible defaults for the text kit:

The research behind this page is in the notes on text, interface and legibility.

Sources