the terminal documentation

How Maya OS counts

A terminal operating system from a Mesoamerica that never collapsed, and reached the first computer before anyone else did. Everything below follows from taking that premise seriously rather than decoratively.

The premise

The conceit is a constraint, not a costume. Every design question gets answered by asking what this civilisation would actually have built, and the answers are frequently better than the ones we got: no floating point, no delete, and a calendar that is genuinely better at being a calendar than the one on your phone.

Two rules run through everything. There are no Arabic digits anywhere a reader can see. And the machine never approximates on its own initiative — where a result cannot be written exactly, the program has to say how much precision it is willing to carry.

Anything written here in dotted form, like 13.0.13.16.14, is Thompson notation: a twentieth-century scholarly convention for transliterating numbers into Arabic digits. It is used in this documentation and in error messages meant for developers. It is not something a reader of Maya OS ever sees.

Numbers

Base twenty, positional, three marks. A dot is one, a bar is five, and zero is a shell — one of the few independent inventions of zero in human history, and a good thing for an operating system to be founded on.

shell, nothing one bar, two dots three bars, four dots four hundred, 1.0.0

Notation has no sign

This is a real constraint, not an oversight to patch over. It is answered in two places.

Machine words use twenties-complement, the base-twenty analogue of the tens-complement real decimal computers used and of the two's complement binary ones use. A word is negative exactly when its leading digit is ten or more, so the sign test is a single comparison — which is what a sign test ought to be. Negative one is 19.19.19.19.19.

Written quantities take an invented marker, ma', from the Yucatec negative particle. It is an invention and is labelled as one everywhere it appears.

The word is a pik

Five digits, because the Long Count already uses five places, so the machine and the calendar agree on the shape of a word. That is 205 = 3,200,000 values, signed from −1,600,000 to 1,599,999.

There is no floating point

None, and no way to make one. Non-integers are exact rationals. A third is a third forever, and every consequence of that is an improvement:

  • No IEEE, no NaN, no negative zero, no infinity.
  • Division by zero is an error. That is the whole of it.
  • 0.1 + 0.2 is not a famous problem here. It is not even writable.
  • The square root of two has no value, because it genuinely does not have one.

That last one is the interesting case. A caller who wants a number must name the denominator they will carry, and the system answers the question actually asked: given that bound, what is the nearest quantity that can be written?

root of two, denominator at most 70 /
the same, at most 1000 /

Time

The Long Count runs k'in, winal, tun, k'atun, b'ak'tun. Everything anchors on 0.0.0.0.0 = 4 Ajaw 8 Kumk'u, 11 August 3114 BCE in the proleptic Gregorian calendar, under the Goodman–Martinez–Thompson correlation constant of 584283 — the one under which 13.0.0.0.0 falls on 21 December 2012.

The one irregularity in the whole system

Eighteen winal make a tun, not twenty, so that a tun lands near a solar year. It is the single place where the count is not vigesimal, and it is why the calendar cannot share a package with the arithmetic: read as pure base twenty, 13.0.13.16.14 is 2,085,534, but read as a Long Count it is 1,877,014 days. A test asserts both, so the boundary cannot quietly erode.

Alongside the Long Count run the 260-day Tzolk'in (thirteen numbers against twenty day names) and the 365-day Haab' (eighteen months of twenty days, plus the five-day Wayeb'). They mesh into the Calendar Round, which returns every 18,980 days — fifty-two Haab'. Only 18,980 of the 94,900 possible pairings ever occur, because the two cycle lengths share a factor of five.

Haab' days are numbered from zero, which is not a programmer's convenience: the first day of a month is its seating, the month taking its seat, so zero-based is the authentic reading.

Also implemented: distance numbers, which monuments carve lowest place first so a reader knows at once they are looking at a span rather than a date; and the Lord of the Night, the nine-day cycle carved in the Supplementary Series.

Units below the day, which had to be invented

The Maya counted days and everything above them. They had no hour, because nothing in their system needed one. A computer does. The Long Count names the units above a k'in as burdens carried on a tumpline; below it the logic inverts, because things shorter than a day are motions rather than loads, and the series descends through what a body can perceive.

UnitValueDurationRoot
k'in124 hday, sun
p'iisk'in/2072 minto measure
xíim/4003.6 minto walk
iik'/8 00010.8 sbreath, wind
muts'/160 0000.54 sto blink
tíip'/3 200 00027 msto flash forth
sáas/64 000 0001.35 mslight

Every unit divides a day exactly, so no conversion here ever rounds. The clock is three digits, p'iis:xíim:iik', which lands within eleven seconds of any instant. A timestamp is iik' since 0.0.0.0.0 — about 1.5 × 1010 today, which needs eight digits and so rides in a double word.

The roots are Yucatec and the meanings are sound. The orthography wants a pass against a proper dictionary before it is treated as settled.

The terminal

Maya writing is not a line of characters. It is glyph blocks arranged in a grid and read in paired columns, and positional numerals run vertically, most significant on top. A line-of-characters terminal fights both, so the substrate changes instead: the screen is a canvas grid of glyph blocks, and there is no xterm.js anywhere in it.

Geometry

UnitSizeHolds
quantum12 × 24 pxone romanised character
band2 or 3 quanta tallthe canonical row
blocksquare, one band tallone vigesimal digit or one Maya sign

Block width scales with band height and is not a constant. A quantum is twice as tall as it is wide, so two quanta of width per quantum of height keeps a block square at any setting: 4 × 2 quanta at a band of two, 6 × 3 at a band of three. At 1200 × 800 that gives 100 × 33 in line regime and 25 × 16 in block regime. The density drop is the central tension of the whole interface, and every layout decision follows from it.

Two registers, which is how the Maya did it

A five-digit vertical number is five blocks tall and a line of prose is one band. They cannot coexist inline, and no layout trick changes that. The resolution is genre, and it is authentic: monuments carve vertically in paired columns, and the codices write tables in horizontal rows.

  • Codex register — the shell, code, calculators, tables. Horizontal runs of blocks, each digit internally vertical.
  • Monumental register — the calendar, the stela, the codex reader. True vertical stacks, optionally in paired reading order.

Bar-and-dot is the plain register. Above it sit the head variants — a portrait glyph for each number from zero to nineteen — and above those the full-figure forms. Same data, three levels of ceremony, exactly as inscription practice worked.

Carving a number

A number is built from its marks, not typed, because that is what a bar-and-dot system is: you are not spelling a number, you are carving it.

KeyMark
F4begin or end a carving
-a bar, worth five. Never more than three
.a dot, worth one. Never more than four
;a shell, which commits a zero at once
spacefinish this digit and begin the next
F9in the scribe, write the file
F2change the band height, anywhere in the system

Nineteen is ---..... A fourth bar would be a digit of twenty, and there is no such thing. The command line is a whole band tall, so a carved digit sits inline in the prompt — which is what the band model was designed for. Scrollback lines holding no carving stay a single quantum tall, so plain text costs nothing extra.

The shell

Chilam is a spokesman: the one who interprets what is said and says it back in words people use. Every name also answers to an ASCII spelling, because a keyboard that cannot type an apostrophe should not lock a reader out of their own machine.

WordMeansDoes
ilto seelist what a house holds
binto goenter a house
tu'uxwheresay where you are
xokto read, to countread a file, or count a carving
ts'íibto writewrite a file
much'to gathermake a house
sáatto let gosend a thing down to Xibalba
ajtz'ibscribethe editor
ajk'inday-keeperthe calendar
k'inday, sunwhat day it is
máaxwhowho is here
áantajhelpwhat can be said

xok doing two jobs is not a pun. The word means both to read and to count, so given a file it reads and given a carving it counts.

Nothing is destroyed

sáat does not unlink. It sends a thing down to Xibalba, the underworld, where it can be found and raised again. A name already taken below is not overwritten — the newcomer is stamped with the Long Count of its descent, because two things let go on different days are different things. A system whose premise is that time is cyclical should not be able to destroy anything.

Kan, the language

Kan is Python as this lineage would have written it. The name is Yucatec for snake, which is both the obvious joke and a real day in the Tzolk'in.

Numerals in source are resolved before Python's own parser sees them, because the Mayan numeral characters are in Unicode category No and Python's tokenizer rejects them outright. A scanner that knows where Python's strings and comments are resolves the numerals in code and leaves everything inside a string or a comment exactly as written. What comes out is ordinary Python.

Then two rewrites and one refusal. Division becomes exact, so one over three is a third rather than a binary approximation of one. Exponentiation goes through the same door, since a negative exponent on an integer is a float in Python and would smuggle one in. And a decimal literal is refused outright — there is no decimal point in this number system and no Arabic digits to put around one, so 0.5 is not a number that can be written, and the error says how to write it instead.

A real program, and its real output:

# -*- coding: kan -*-
DAYS_PER_WINAL = 
DAYS_PER_TUN   = DAYS_PER_WINAL * 

year = rational(, )

print(DAYS_PER_TUN)            # 360
print(DAYS_PER_TUN / year)     # exactly 72/73
print(root(, ))          # 99/70
prints / /

The # -*- coding: kan -*- line is not decoration. Kan registers a real source encoding through the mechanism PEP 263 provides for any other encoding, so a file marked this way imports with its numerals already resolved.

Architecture

The Go server is the terminal emulator and the browser is a display. This falls out of the block model: nothing in the stack is a real VT220, so it should not pretend to be one. The server owns the block buffer and runs the utilities; the display draws blocks and reports keystrokes, and holds no application logic at all. That is how a thin client is supposed to work, and how the first timesharing systems actually did.

user program (WASM: Kan, C, Go)
      │ stdout bytes
      ▼
Go terminal emulator ── block buffer, band grid, cursor
      │ diffed block ops over WebSocket
      ▼
TS canvas renderer ── draws blocks, captures input

Nothing in the page scrolls and there is no scrollbar anywhere. The server owns the scrollback; the display sends a wheel as a number of lines and gets a screen back.

Frames carry only the cells that changed. Digits are drawn procedurally from their geometry — bars, dots and a shell — so there is no font, no asset pipeline, and a digit is exact at any size.

A block hides the quanta beneath it and a frame does not send them: the anchor carries the whole rectangle. Any client of this protocol has to lay the covered cells down itself, or whatever was there before goes on being drawn through the gaps between the bars.

Untrusted code is planned to run as WASM under wasmtime rather than in containers: no syscall surface, fuel-metered, snapshot and resume for free, and one machine serves many readers. That sandbox is not built yet, which is why Kan does not run from the shell today.

What exists

Numbers
built, tested
Calendar
built, tested
Block terminal
built, tested
Shell and filesystem
built, tested
Kan
built, tested
WASM sandbox
not started
Accounts
not started
C and Go
not started
Maya script glyphs
not started

Kan runs and is tested, but it cannot yet be run from the shell, because doing that safely needs the sandbox. Running reader-submitted code any other way on a shared machine is the one thing on this project not worth improvising.

Maya script is logosyllabic and is not encoded in Unicode — proposals exist, nothing is standard — so a glyph layer means a custom font over the Private Use Area, with the roughly eighty common syllabograms as the tractable subset. The numerals are encoded, at U+1D2E0 to U+1D2F3, added in Unicode 11.0; font coverage in the wild is close to nonexistent, so those characters are used for source code and interchange while anything on screen is drawn from its geometry.

Open questions

  • Band height of two quanta or three. Bigger blocks read better and cost a third of the rows. F2 switches it live, which is the only honest way to settle it.
  • Monumental reading order: true paired columns, which is authentic and genuinely disorienting to a modern reader, or a single column, which is readable and half-authentic.
  • Whether the shell prompt should live in the codex or the monumental register.
  • Yucatec orthography throughout, against a real dictionary.
  • The Lord of the Night anchor — G9 at 0.0.0.0.0 — is the one part of the calendar taken on less certain footing than the rest. If it is wrong, one constant fixes it.