luvcraft/birthday/marquee.lisp
--------------------------------------------------------------------- The letterforms
Five-by-seven bitmaps for the characters a birthday banner needs; each pixel is drawn two cells wide so the letters read square on a terminal grid. Anything the font does not know renders as blank space, which for a marquee is the right kind of failure.
Render text through the marquee font into seven strings of pixels.
A dozen blank columns end the cycle, so the sign goes briefly dark between one pass of the name and the next instead of chasing its own tail around the wrap.
--------------------------------------------------------------------- The frames
A frame is one full ANSI repaint: clear, home, then every row. The banner scrolls right to left with wraparound; ink hue drifts with the scroll so the rainbow rides the letters; the light strips above and below chase the other way. Colour changes are emitted only at column boundaries where they change, which keeps a frame to a few kilobytes.
One row of chase lights: every fourth cell lit, walking with phase.
The whole ANSI frame for the marquee at scroll phase.
Every row is cursor-addressed rather than newline-separated: a newline after the last row of an exactly-filled screen scrolls it, which is how the sign's top row of lights once quietly walked off the wall.
The upper light strip, the letters centred between the strips, and the lower strip chasing the other way.
Two screen cells per font pixel, scrolling leftward.
--------------------------------------------------------------------- The display
How often the marquee advances one pixel column: about eleven a second, the pace of a sign worth reading rather than a strobe.
The wall clock, not the session clock: the sign keeps its own beat the way the tape orb does, clamped so a stall does not fast-forward it.
--------------------------------------------------------------------- Mounting it
Mount the marquee wall and start its animation; returns the display.
Places a width by height rectangle of terminal blocks at origin facing
-z (the :back face), discovers it as a terminal surface, and dresses it
in a marquee display showing text. Placing the same blocks again on a
reopened world is a no-op edit, so mounting is idempotent.
Take the marquee display down, leaving the wall blocks standing.
Rows of X-and-space pixels, seven per glyph.
Test whether two compatible scalars are equal.
Multiplication and scalar scaling.
Subtraction or unary negation.
The minimum of compatible quantities.
The maximum of compatible quantities.
Addition over compatible quantities.
(stream red green blue)(stream columns phase)The non-negative remainder of integer division.
Division of two represented quantities.
(banner-rows banner-width columns rows phase)The whole ANSI frame for the marquee at scroll PHASE. Every row is cursor-addressed rather than newline-separated: a newline after the last row of an exactly-filled screen scrolls it, which is how the sign's top row of lights once quietly walked off the wall.
Logical conjunction of tests and raw truth values.
Test whether one compatible scalar is less than another.
How often the marquee advances one pixel column: about eleven a second, the pace of a sign worth reading rather than a strobe.
(overlay session)Publish any complete pending render state for OVERLAY at a frame boundary.
Convert one scalar float or unsigned value to a 32-bit float.
Logical disjunction of tests and raw truth values.
Test whether one compatible scalar is at least another.
(terminal text)(session &key (text "ALEX 4")
(origin '(-4 15 -9))
(width 9) (height 3)
(columns 72) (rows 11))Mount the marquee wall and start its animation; returns the display. Places a WIDTH by HEIGHT rectangle of terminal blocks at ORIGIN facing -z (the :back face), discovers it as a terminal surface, and dresses it in a marquee display showing TEXT. Placing the same blocks again on a reopened world is a no-op edit, so…
(session)(world origin-x origin-y origin-z face width height
&key (material *terminal-block*))Place a WIDTH by HEIGHT terminal-material rectangle in resident WORLD. ORIGIN is the lower-left block as seen from FACE's outward side.
(world x y z face
&key (material *terminal-block*)
(absent-neighbor-policy :air))Discover the maximal exposed rectangular terminal surface at X,Y,Z,FACE. Return the surface and :RECTANGLE. A seed which is not terminal, is covered, or belongs to a non-rectangular coplanar component instead returns NIL and a descriptive status. Discovery runs the compiled TERMINAL-SURFACE-DISCOVERY program; this…
(session surface columns rows
&key (class 'terminal-display)
(fixture (terminal-display-fixture))
(margin 0.12)
(font-scale 1.0)
(font-pathname *terminal-display-font-pathname*)
(bold-font-pathname *terminal-display-bold-font-pathname*)
(default-foreground *terminal-display-default-foreground*)
(screen-role :terminal-screen)
(faceplate-role :terminal-faceplate)
(add-p t))Build a COLUMNS by ROWS Ghostty terminal display of CLASS on SURFACE. SURFACE is anything answering the terminal-surface protocol: a wall of blocks or a phone screen. SCREEN-ROLE and FACEPLATE-ROLE name the shader materials of the panel behind the text and the glass in front of it; the defaults are the wall's tube. …
(session overlay &key (release-p t))Detach OVERLAY at a frame boundary and optionally release it there. The no-release path is intentionally preserved for FUSE-LUVCRAFT-OVERLAY: that path may run inside a frame whose command stream still borrows OVERLAY.
The marquee: a terminal wall wearing the birthday's name in lights.
A rectangle of terminal blocks mounts over the gazebo's entrance, and a
birthday-marquee-display-- an ordinary Ghostty terminal wall -- shows big block letters scrolling across it, figlet-fashion, in rainbow ink with chase lights above and below. No pty is involved: the marquee writes ANSI frames straight into its own Ghostty emulator and marks itself dirty, and the wall's stock refresh path re-snapshots the screen and republishes the glyphs, exactly as it would for a shell's output. The wall's ink is emissive above the lens's bright-pass threshold, so the letters glow into the dusk on their own.