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.
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.2is 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?
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.
| Unit | Value | Duration | Root |
|---|---|---|---|
| k'in | 1 | 24 h | day, sun |
| p'iis | k'in/20 | 72 min | to measure |
| xíim | /400 | 3.6 min | to walk |
| iik' | /8 000 | 10.8 s | breath, wind |
| muts' | /160 000 | 0.54 s | to blink |
| tíip' | /3 200 000 | 27 ms | to flash forth |
| sáas | /64 000 000 | 1.35 ms | light |
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
| Unit | Size | Holds |
|---|---|---|
| quantum | 12 × 24 px | one romanised character |
| band | 2 or 3 quanta tall | the canonical row |
| block | square, one band tall | one 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.
| Key | Mark |
|---|---|
| F4 | begin 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 |
| space | finish this digit and begin the next |
| F9 | in the scribe, write the file |
| F2 | change 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.
| Word | Means | Does |
|---|---|---|
| il | to see | list what a house holds |
| bin | to go | enter a house |
| tu'ux | where | say where you are |
| xok | to read, to count | read a file, or count a carving |
| ts'íib | to write | write a file |
| much' | to gather | make a house |
| sáat | to let go | send a thing down to Xibalba |
| ajtz'ib | scribe | the editor |
| ajk'in | day-keeper | the calendar |
| k'in | day, sun | what day it is |
| máax | who | who is here |
| áantaj | help | what 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
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.
F2switches 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.