ASCII / CRT Terminal ASCII / CRT 终端
Tranquility.log·1:00·Lemo-Opuscarby Lemomo
Everything on screen is a real character on a monospace grid, lit by the phosphor of an old monochrome CRT.
The demo: A moon-base AI wakes after forty years to a signal from Earth, and replies by drawing “home” in characters.
Everything on screen is a real character on a monospace grid, lit by the phosphor of an old monochrome CRT. References (grammar only): 1970s–80s monochrome terminals and teletype logs (typed output, cursor, status lines); ASCII art and demoscene text-mode pictures (images from glyph density); analog-synth film scores of the early 1980s (cold pulse, patient drones). Copy no film's interface text, fonts or logos, and use no real agency names.
1. Essence, and what it is not
A single monochrome terminal screen: P3 amber (or P1 green) phosphor glowing on dark glass, curved at the edges, with scanlines. Every image, including title, landscape, face, room and subtitles, is made of real glyphs placed on a character grid and chosen by ink density. The film has one camera, the screen itself. It moves only by changing the font size of the grid: bigger characters push in, smaller characters pull out.
The mood is cold, patient and machine-calm: typed text, a blinking cursor as suspense, and one warm surprise.
Never fake it with an image plus an "ASCII filter". Characters must be drawn as characters on a grid. Not a hacker-movie green rain, not a pixel-art game (no sprites, no free pixels), not a sci-fi HUD (no vector lines, no free-floating panels).
2. Materials & rendering
- Font: one monospace bitmap-style font (VT323, OFL); cell 0.4 em × 0.8 em (1:2). A large prerendered glyph atlas with a stroke for beam spread, scaled with
drawImage, keeps zooms continuous and sharp. - Grids: column count per shot (title ~20, terminal 80, picture 200+); a push-in can go until one cell holds a sub-grid of its own.
- Density ramp: each glyph's measured ink coverage × dim / normal / bold → about 20 levels, near-duplicates dropped; a small ordered-plus-hash dither per cell; cells below a threshold stay blank. Negative space makes ASCII art readable.
- Picture pipeline: paint grayscale into an offscreen "negative" aligned exactly to the grid (several samples per cell; channels for phosphor, second colour, a flag), average per cell, pick a glyph. Interiors get local contrast (
m + 0.9·(m − mean)) to keep outlines. - Recognisable subjects: silhouettes, not noise (real coastline polygons, a face from a few planes); dark = sparse dim glyphs, bright = dense bold, shadow sides sparse
.only; an angle with a clear silhouette. - Depth on a grid: far = darker and sparser, near = brighter and denser; a thin bright rim separates planes.
- CRT post (WebGL, adapted from
core/post/crt.js):- Phosphor colour: luminance maps along the phosphor's ramp (dim deep orange → amber → overexposed near-white for P3).
- Glow: multi-level bloom plus a faint halo.
- Glass: fine scanlines (narrower on bright areas), slight barrel curvature, rounded mask, warm near-black unlit glass, faint reflection, ~1 % flicker, fine noise. No RGB grille: monochrome tubes have none.
- Afterglow: text that disappears decays with τ ≈ 0.1 s, computed analytically so every frame is deterministic. Moving glyphs get one faint trail sample.
3. Colour logic
- One phosphor carries everything: amber, green or white, in its own brightness ramp. No second hue for decoration.
- At most one soft second colour, used for one thing only (the emotional object), switched on at a single moment, on a downbeat, and never taken back casually.
- Examples: amber screen with an ice blue-white for something far away; green screen with a warm pink for a living thing; white P4 screen with a pale gold for a memory.
- Brightness is the only other tool: dim / normal / bold and inverse video.
4. Type & subtitles
- Subtitles are terminal output. A reserved LOG strip at the bottom: a dim separator, an inverse-video tag, then a prompt and the line in uppercase at ~45 px. It is typed word by word in sync with the voice (whisper word timestamps) and holds ≥ max(1.8 s, speech + 0.6 s). It lives outside the zooming grid, like a status line, with the same scanlines and glow. When nobody speaks it can show system state (a progress bar, a waiting message).
- On-screen text that the story shows is picture, not subtitle; in the
.srtit appears as[SCREEN] …. - Titles are typed with key clicks, as a filename, a command or a header.
5. Motion quality
- Characters never move off-grid, except in a declared transformation moment. Typing, scrolling and counters jump from cell to cell. A roller flips digits discretely, fast then slowing.
- Only three things move continuously: grid scale (the camera), CRT physics, and particles in a transformation.
- Typing speed is performance: machine output is even and fast (~10–40 ms/char); dramatic lines slow to note values of the score; human typing is hand-timed, uneven, with pauses and bursts.
- Particles spawn from random points inside source glyphs, start small, fade in, ease to their cell, and flash briefly on landing, so they never stack into bright bars.
- Push to a character: hold where the whole glyph reads, then push until its inner text reads; inner text full brightness, outside text a few percent, neighbours dimmed, so the shape reads before the text.
6. Camera grammar
The only camera is the grid scale, plus CRT power states and one possible shot outside the screen.
| Move | What it expresses | Can serve |
|---|---|---|
| Static 80-column terminal | Waiting, procedure, machine time | a countdown; a queue; a log being read |
| Push in (cells grow) | Attention, intimacy | one line of dialogue; a name; a decision typed |
| Pull out (cells shrink) | Context, scale | a title becoming line 1 of a list; one entry among thousands |
| Push into a single glyph | Hidden depth; what a symbol is made of | a letter made of messages; a pixel made of people |
| Scroll (the buffer moves) | Accumulation, time passing | years of records; a changelog; a feed |
| Clear screen / hard redraw | A cut; a new state | a reboot; a new chapter; a crash |
| Text becomes image (grid shrinks as glyphs fall) | Words turning into world | a letter becoming a face; data becoming a map |
| Power on / off, degauss | Birth, death, sleep, shock | a machine waking; an ending; a jolt |
| Pull back off the glass into a drawn room | The screen is an object in a world | who is reading; where the machine is |
Opening and ending come from the topic. Framing: text stays in the safe area inside the curvature; keep the LOG strip clear. Transitions happen on the screen (clear, redraw, scroll, power), never a crossfade between two screens.
7. Sound palette
- Music: an original analog-synth score, rendered in code (numpy / numba): PolyBLEP saw and square oscillators, a resonant 4-pole ladder filter with moving cutoff, filter envelopes, square-wave sequencers, slow tape wow (± a few cents), long reverb on pads, gated reverb on hits; options also a sub-bass drone, sample-and-hold noise, a detuned unison lead. Never chiptune.
- Technique options: grow a single arpeggio as an image forms; hold the only major chord for one moment; drop everything, reverb tails included, to digital zero on a shock; open a chord on the colour change.
- Silence is the main instrument: room tone alone under typing and blinking cursors; a short near-silence before a transformation.
- Foley (all synthesised): relay click and degauss hum (mains hum plus harmonics with a thump); buckling-spring keys (plastic click, spring ping, key-cap thock; duller space bar, heavier Enter); teletype ticks for remote text; modem and phone-line tones (answer tone, FSK, band-limited noise); square-wave error beeps and BEL; clusters of tiny high ticks for particles; a dot-matrix printer or tape drive as options; room tone (fan, low hum, a very quiet flyback whine).
- Mix: music ducked about −8 dB under the voice; master −14 LUFS with no grain in the mux (the noise lives in the picture).
- Voice: when a machine speaks, process it (light ring modulation, a short comb, a telephone-ish band-pass, soft saturation) and check that whisper still reads every line. Few short lines.
8. Native moves
A menu: use the ones your story needs.
- The picture is made of words. A sentence breaks apart and its letters, sorted by ink density, become an image containing only those letters. Fits content like: a manifesto becoming a crowd; a recipe becoming the dish; a poem becoming a landscape.
- Font size = camera. Push until one character fills the screen and is itself made of smaller text. Fits content like: a company name made of employee names; a year made of its headlines; a heart glyph made of messages.
- Typing rhythm = editing rhythm. Even machine typing against uneven human typing; a blinking cursor is a performance. Fits content like: a job application; an apology; a password remembered.
- CRT physics. Power on as dot → line → frame, off to a dot; afterglow, bloom, curved glass. Fits content like: a hospital monitor; a night shift ending; an old archive reopened.
- A single colour change. One soft second colour appears once. Fits content like: a first green leaf in a data log; a blood-orange sunset in a weather report; a child's drawing in a server room.
- The log file. Timestamps, error codes, progress bars tell the story. Fits content like: a product launch day; a rescue; a migration.
9. Pitfalls of the medium
- Inverse video:
destination-outonly changes alpha, which the upload ignores → opaque black canvas, cut letters with a black atlas. - Negative not aligned to the grid drifts across hundreds of columns → map with cell width / samples exactly.
- Wrong cell centre: cells are 2·cw tall, so the centre is
(j + .5)·2cw, not(j + 1)·cw. - Every dark pixel becomes a character (
-+-+walls) → threshold, gamma ≈ 1.25, local contrast. - Noise-textured subjects read as textured blobs → silhouettes, a dark sparse background, a dot-only shadow side.
- Particles from letter centres stack into bars → random points inside the glyph, small, faded in.
- Stroked atlas glyphs close their counters (
M,B) → sample a thin fill-only glyph bitmap for masks. - Lazily built data never reaches the exporter → build particle schedules and their sound events before
window.EVis exported. - A power-on parameter at 0 still draws the centre dot → exposure 0 before power-on, multiply the dot term by exposure.
10. Engine
In demo/: term.js (glyph atlas, grid drawing, density ramps, image → cells), art.js (procedural grayscale scenes for the negative), crt.js (monochrome phosphor CRT post, WebGL2), main.js (timeline, events EV, subtitles SUBS), voice_fx.py (machine-voice processing), mix.py (synthesised foley + ducking), music/score.py (analog-synth score), tools/subs.mjs (export window.SUBS). File map and commands: DEMO.md.
11. Variation space
You decide who types, what the screen is for, the grids, the opening, the ending, the camera (grid) path, the pacing and whether the second colour appears at all. All far from our demo:
- Structures: a chat log (two users typing to each other across one night, timestamps as scene headers); a system manual (chapter headers, diagrams in ASCII, one page going wrong); a boot-to-shutdown day (one machine's day as a sequence of scheduled jobs, each drawing a picture of what it does).
- Openings: mid-scroll (a buffer already racing past; it stops on one line); a printout (a dot-matrix sheet feeding up, then the screen takes over); an error first (a crash dump, then we rewind to what caused it).
- Endings: the buffer fills (the story's text scrolls up and away until only the prompt remains); the screen saver (after the last line, a slow drifting pattern takes over the idle screen); the cursor stops blinking (no power-off, just a final state held).
How our demo was made (story, shots, score, end card, build): DEMO.md. Read it after your treatment exists.
Director's notes for the demo film
One example among many. Don't reuse its story, arc, shots, props or timings.
Demo: TRANQUILITY.LOG (59.8 s) · ascii-crt.mp4 · source in demo/
Brief the demo was made to: a 30–60 second film in the ASCII / CRT Terminal style.
Story & structure
A machine left on the Moon has logged NO SIGNAL for 40 years. A message arrives; it replies I AM STILL HERE., and the letters of that reply become an earthrise. We picked a story where text is the only way the character can act: a machine, a remote operator, a message across distance or time. How the native powers were used:
| Native power | Story use |
|---|---|
| The picture is made of words | The climax is a sentence turning into an image. The reply I AM STILL HERE. breaks apart and its letters, sorted by ink density, become an earthrise. The image contains only the letters of that sentence. |
| Font size = camera | Push in until one character fills the screen. That character is itself made of smaller text, which reveals scale and meaning. One M is made of 40 years of NO SIGNAL logs. |
| Typing rhythm = editing rhythm | Machine typing is even and fast. Human typing is uneven, with pauses and bursts. A cursor blinking three times is the most important performance in the film. |
| CRT physics | The screen powers on as a dot, then a line, then the full frame, and powers off by collapsing to a dot. Used for the opening and the ending. |
| A single color change | Blue-white #BFE4FF for Earth, the only non-amber thing, is the emotional peak. Switched on a musical downbeat. |
Adapting ideas we noted while writing it (examples, not rules): a product launch becomes a spec sheet that assembles into the product's silhouette; a love letter becomes its own words raining into a face; a history lesson becomes a log file whose dates zoom out into a map.
Emotional arc (59.8 s): dark and a cursor, then waking (boot log, a date jumping), then disturbance (handshake noise, a line of text), then holding breath (cursor blinks), then setback (NOT FOUND ×3 on three beats), then decision (typing the reply), then near-silence for 1–1.5 s, then the climax (letters become the image, the one color appears), then the scale reveal, then waiting, then a tiny reply, then an echo (pull out of the screen, power off to a dot).
Shots
| Beat | Camera |
|---|---|
| Opening | CRT off, then a dot, a line and the full frame. A giant cursor blinks 3× in empty space. |
| Title | The title is the file the machine opens, typed at 20 columns. It then pulls out to 80 columns as it becomes line 1 of the boot log. |
| Suspense | Static 80-column terminal. The only motion is text and the cursor. Hold on blinks. |
| Decision | Push in from 21 px to 80 px cells around the prompt. The reply is typed huge and centered. |
| Climax | The grid shrinks while falling (80 px letters become 8 px cells). Hold on the finished picture, and switch color on the downbeat. |
| Scale reveal | Push into one glyph until it becomes its own sub-grid of text, then snap back. |
| Ending | The only shot outside the screen: pull back until the screen image sits inside a character-drawn terminal in a dark room. Next to it is a large porthole showing the real Earth (18–20 rows tall). Only two things glow: the tiny reply and the planet. Then power off to a dot. |
Why this route, for this story: the title typed as a file the machine opens made the machine the narrator; the push-in on the reply made the decision intimate; the only shot outside the screen came last, so the room and the real Earth were a reveal. Each of these is one choice from STYLE.md §6, not the style's default opening or ending.
The letter fall (the demo's climax, step by step)
- Hold the sentence for 1.5 s with the cursor blinking.
- At the split, letters jitter and throw a ghost copy.
- Each target cell spawns a particle from a random point inside a matching source letter (only letters of the sentence exist in the ramp). The particle starts at 18 px, shrinks to the grid size and fades in over 0.2 s so particles never stack into a bright bar. Its path eases smoothly in x, with y = u·(0.6 + 0.4u).
- Ground lands bottom to top.
- Earth letters first all fall behind the horizon, then rise into place from bottom to top, so the planet literally rises.
- Source letters dim as they are used up.
- The landing flash is 1.9× brightness decaying over 0.12 s.
Fractal push-in: push to a framing where the whole M is clear and hold 1 s, then push further until the log text is readable (cell 450 px, sub-text about 28 px). Text inside the glyph is at full brightness, text outside is at 3–6%, and neighbor glyphs are at 20%.
Typing speeds used: boot lines 12 ms/char, commands 38 ms, title and reply 120–150 ms (sixteenth notes at 100 BPM). Human typing uses hand-timed offsets with pauses.
Score structure
- Music: an original Carpenter-style analog synth score, rendered in numpy and numba.
- Sound sources: PolyBLEP saw and square oscillators, a resonant 4-pole ladder filter with moving cutoff, and filter envelopes. Add slow tape wow (±3 cents), a long reverb on the pad and gated reverb on the hits.
- Grid and key: D minor at 100 BPM, with every cut on a bar or beat.
- Cue shape: silence under boot, then a drone and a square-wave 16th sequencer while the machine wakes.
- The hard cut: on the handshake the whole score drops to digital zero, reverb tails included, for five seconds.
- Climax: a single saw arpeggio grows as the letters fall and opens into Bbmaj7 on the color change. The only major chord (D major) lands on the reply.
- Never chiptune.
- Silence is the main instrument. Handshake, message and cursor blinks play over room tone only, and there are 1.5 s of silence before the picture forms.
- Foley (all synthesized):
- Relay and degauss: relay click, degauss hum (60 Hz plus harmonics decaying over 0.35 s, with a thump).
- Keys: buckling-spring keys (plastic click, a 3.4 kHz spring ping, key-cap thock). The space bar is duller and Enter heavier. Remote characters get a dry teletype tick.
- Modem: an original handshake (2100 Hz answer tone with two phase reversals, a dual tone, 1200/2400 FSK, scrambled band noise, all band-limited to a phone line).
- Beeps: a square-wave error beep, a lower one for the final error, and a 1 kHz BEL for the reply.
- Particles: particle landings as clusters of tiny 2–6 kHz ticks.
- Room tone: a fan bed with 120 Hz hum and a very quiet 15.7 kHz flyback whine (about −50 dBFS before loudness normalization).
- Voice: the machine's log, read by Kokoro
am_echoat speed 0.8. Process it with 16% ring modulation at 52 Hz, a 6 ms comb (0.22), a 140–7000 Hz band-pass and soft saturation. Whisper still reads it 5/5. Use 4–6 short lines. Under the voice the music is ducked about −8 dB. The master is −14 LUFS with no grain (the noise lives in the picture).
Palette & props
- Font and grids: VT323 atlas prerendered at 160 px with a 2.2 px stroke. Grids used: title 20 columns (cell width 84 px), terminal 80 columns (21 px), picture 240 columns (8 px), room 213 columns (9 px), fractal push-in up to a 450 px cell with a 40-column sub-grid inside each cell.
- Ramp values: attributes dim / normal / bold = 0.42 / 0.7 / 1.0; dither amplitude 0.7; cells with luminance < 0.05 stay blank. Negative sampled 3×6 per cell: R = amber luminance, G = second-color luminance, B = a flag such as "night side". Rooms get a 3×3 local-contrast boost.
- Earth that reads as Earth: continent polygons in lon/lat (rasterized once to an equirectangular mask) rather than noise. Ocean albedo 0.14 (sparse dim glyphs), land 0.96 (dense bold glyphs), clouds 0.5 (mid-density swirls). A narrow terminator (smoothstep −0.03…0.10) and the night side as sparse
.only. Angle with a clear silhouette: Africa and Europe. - Lunar ground: far ground darker and sparser (×0.3), near ground brighter and denser. The horizon gets a thin bright rim (10 px falloff). Craters: the left inner wall is in black shadow, the right inner wall is lit, the rim lip is bright. A few big defined foreground craters.
- CRT post (
demo/crt.js): R maps to amber: dim#FF6605, mid#FFB000, overexposed toward#FFEBB8. G maps to#B8E0FF. Four-level bloom at 0.55 plus a halo at 0.22. 3 px scanlines at 0.3; barrel curvature 0.045; unlit glass#090604; faint top-left reflection, 1.2% HV flicker. - Afterglow: τ = 0.12 s; one extra trail sample at t − 35 ms × 0.3.
- LOG strip: dim dashed separator, inverse-video
LOGtag,>and the line in uppercase VT323 at 45 px; holds 1.4 s after the line is complete; idle statesTRANSMIT [#####.....] 40%,SENT … AWAITING REPLY. - Title: the filename, typed with key clicks, plus a small dim sub-line.
End card
Typed over the final room shot (ASCII / CRT TERMINAL, LemoLab × Claude Opus 5.5), followed by the CRT power-off. The "LemoLab × Claude Opus 5.5" sign-off belongs to this library's demo only; a user's film carries no LemoLab credit and no copy of this card.
Build notes
styles/ascii-crt/demo/
term.js glyph atlas, grid drawing, density ramps, image → cells
art.js continent texture, earth shading, lunar ground, craters
crt.js monochrome phosphor CRT post (WebGL2)
main.js the whole timeline: boot, handshake, message, reply, letter fall, fractal, room, end card, EV, SUBS
voice_fx.py AI voice processing mix.py synthesized foley + ducking + score
music/score.py original analog-synth score tools/subs.mjs export window.SUBS
node core/render/still.mjs styles/ascii-crt/demo --range 33.4:37.4:0.2renders frames of any section;?frame=earth|fractal|base|posterrenders single tests;?nosub=1renders without the LOG strip.sh styles/ascii-crt/demo/build.shgoes from zero to the finished film: TTS, voice FX, whisper, score, events, mix, SRT, render (1435 frames in about 50 s with 3 workers), mux, styleframe, poster.
Pitfalls tied to this demo's props:
- Offscreen negative drift showed up as doubled stars and misaligned crater edges across 200 columns.
- A noise-textured Earth reads as a textured ball. Use continent silhouettes, a dark sparse ocean and a dot-only night side.
- Particles bursting from 13 letter centers stack into a glowing bar.
- Earth letters rising through the still-visible sentence look messy. Send them all behind the horizon first, then rise.
- The thick (stroked) atlas glyph closes the gaps of
M. For the fractal mask, sample a separate thin (fill-only) glyph bitmap. - Lazily built data: without building the particle schedule first, the foley for the fall was silent.




