---
title: Eighty by Twenty-Five
date: 2026-08-21
tags: [runawaydevil, terminal, design]
---
# Eighty by Twenty-Five

This system is 80 columns by 25 lines. Not "about 80". Not "80 on desktop".
Eighty, on a phone, on a 4K panel, in a window you have dragged to the size of
a business card. What changes with your screen is how large one cell is drawn,
and nothing else.

That sounds like a decoration. It is closer to a constitution: almost every
awkward decision in this codebase comes from refusing to break it.

## What the browser wants to do instead

Every terminal library for the web is built around the opposite idea. You give
it a container, it measures the container, it reports back "you have 142
columns and 38 rows", and the program on the other end reflows to match. That
is correct for a terminal that hosts *someone else's* program.

Here there is no other program. The system is the only thing writing to the
screen, so the size can be fixed and everything above it can be composed
against a known width. That is why the terminal in this site is written here
rather than pulled in — not because the libraries are bad, but because their
central feature is the one thing that had to go.

## Nothing measures text with .length

The first real casualty was string length. `"日本語".length` is 3, and it
occupies 6 cells. `"coração"` measured after Unicode normalisation can be 8
code points and 7 cells. A line with colour in it —

```text
ESC[91mRUNAWAY ESC[0m
```

— has fourteen characters of escape that occupy nothing at all.

So there is exactly one module allowed to reason about horizontal space, and
it counts *cells*: grapheme clusters, East Asian widths, combining marks, and
an ANSI scanner that skips what is invisible. Everything else calls it. A test
fails if any composed screen exceeds cell 80, which is how a typo gets caught
before it becomes a crooked box.

## Three defences, because one is not enough

1. **The model cannot exceed it.** The screen allocates exactly 2000 cells.
   Writing past column 80 wraps. There is no code path that grows the grid.
2. **Composition measures in cells.** Every listing, every header, every piece
   of art is measured before it ships. The art validator refuses to build a
   piece 92 cells wide.
3. **The renderer fits, never reflows.** Playwright asserts `cols === 80` and
   `rows === 25` at 3440×1440, at 1280×720, on a tablet and on a phone.

## The part that took three tries

Filling the screen is where the constraint bites. An 80×25 grid has an aspect
ratio near 1.6:1; a modern monitor is closer to 1.9:1. Scale the grid until it
fills the height and the glyphs come out around 38 pixels tall — legible, and
absurd for reading prose. Cap the size and a wide screen shows a lot of black.

There is no arrangement that satisfies both. So the system does the honest
thing: it renders at the size an 80×25 terminal has always been — 640 by 400
pixels, cells of 8 by 16 — and lets the surround be black. The frame is a
hairline, so the emptiness reads as a terminal in a room rather than a page
that failed to load.

It also stopped scaling with a CSS transform, which was quietly resampling
every glyph. Setting the font size instead means the browser rasterises at the
real size. The text got noticeably sharper the day that changed.

## Why bother

Because the limit does the editing. Seventy-six usable columns is roughly the
width prose wants anyway; the constraint just makes you notice. A pager forces
an argument to have parts. Sixteen colours force contrast to mean something.

None of that is nostalgia. It is a set of limits that happen to produce better
decisions than no limits at all.
