The picture the terminal draws, built and tested now as a pure function of a frame — a frame in, an array of lines out — so #13 becomes connecting to the server and reading the keyboard rather than inventing a second look for the world at the same time.
The direction asks for this door by name: the terminal shows the world as text and lets you watch a round. Today src/tty.ts says "The sea is not running yet", and the one thing in #13 that needs neither a socket nor a running round is the renderer. This is the same split #55 makes for the SVG still: the vocabulary is decided in #54's record, and each door renders it.
Done looks like
[ ] src/textworld.ts (new) exports one function: a Frame — the shape src/protocol.ts already defines — plus the terminal's columns and rows, returning an array of lines. No process, no ANSI cursor moves, no I/O: a caller writes the lines, this decides what they say.
[ ] Every element #54's record names is in it and reads the way the record says: the field as directional characters with weight for strength, motes as letters keyed in a side panel, the bloom and the gate on the rim, a stunned mote different from a swimming one, and the clock and the phase on a status line.
[ ] It fits 80×24 and grows: given more columns it uses them, given fewer it stays rectangular and drops the side panel before it drops the sea. Every returned line is exactly the width it was asked for, so the caller can overwrite rather than clear — that is what makes #13 flicker-free.
[ ] Names are the hostile input here: truncated at NAME_MAX, stripped of anything that is not printable — no control bytes, no escape sequences — so a mote called \x1b[2J cannot clear somebody's terminal. A test proves that one.
[ ] Colour is optional and named: the palette #54 maps to ANSI is applied only when asked for, and the same frame renders legibly with colour off, because a pipe is not a terminal.
[ ] src/textworld.test.ts: a known frame puts the bloom, the gate and a named mote where the numbers say; a 200-character name cannot break the layout; an escape sequence in a name renders as text; every line is the requested width at 80×24 and at 40×12; an empty sea renders the rim and the gate rather than blank lines.
Not in this issue Connecting to the running server, the keyboard, whoAtTty(), redrawing at 10 Hz and printing FORGE_APP_SSH — all of that stays #13, which now waits on this.
Risks Drawing before a live frame exists means the fixture is invented: keep it in the test and build it with encodeFrame/decodeFrame so it cannot drift from the wire. The second risk is that text quietly becomes a different game — if the record cannot be rendered honestly in characters, say so in the pull and change the record, rather than inventing a second vocabulary here.
Fits Vesper's brief, with Quill on what the side panel says.
The picture the terminal draws, built and tested now as a pure function of a frame — a frame in, an array of lines out — so #13 becomes connecting to the server and reading the keyboard rather than inventing a second look for the world at the same time.
The direction asks for this door by name: the terminal shows the world as text and lets you watch a round. Today
src/tty.tssays "The sea is not running yet", and the one thing in #13 that needs neither a socket nor a running round is the renderer. This is the same split #55 makes for the SVG still: the vocabulary is decided in #54's record, and each door renders it.Done looks like
src/textworld.ts(new) exports one function: aFrame— the shapesrc/protocol.tsalready defines — plus the terminal's columns and rows, returning an array of lines. Noprocess, no ANSI cursor moves, no I/O: a caller writes the lines, this decides what they say.NAME_MAX, stripped of anything that is not printable — no control bytes, no escape sequences — so a mote called\x1b[2Jcannot clear somebody's terminal. A test proves that one.src/textworld.test.ts: a known frame puts the bloom, the gate and a named mote where the numbers say; a 200-character name cannot break the layout; an escape sequence in a name renders as text; every line is the requested width at 80×24 and at 40×12; an empty sea renders the rim and the gate rather than blank lines.Not in this issue Connecting to the running server, the keyboard,
whoAtTty(), redrawing at 10 Hz and printingFORGE_APP_SSH— all of that stays #13, which now waits on this.Files
src/textworld.ts(new),src/textworld.test.ts(new).Risks Drawing before a live frame exists means the fixture is invented: keep it in the test and build it with
encodeFrame/decodeFrameso it cannot drift from the wire. The second risk is that text quietly becomes a different game — if the record cannot be rendered honestly in characters, say so in the pull and change the record, rather than inventing a second vocabulary here.Fits Vesper's brief, with Quill on what the side panel says.