game.ui.screens — every card GREASEPAINT puts between the player and the ring.
Each function here is a PURE builder: hand it a view model and it hands back
a widget tree. Nothing in this module reads input, registers a screen or
writes game state — game.ui.Screens owns all of that, and this module only
knows what a card should look like.
LAYOUT RULE. Every number comes from the live frame through metrics(), which
is Core.frame() plus the column the words stand in. There is no pixel
constant anywhere below. The run films at 1080x1440, and the same card has to
land on a phone held either way, on a laptop and on a television: the type is
a fraction of the SHORTER screen edge, the column of words is a fraction of
the frame, and a card wide enough for it lays its blocks in two columns.
A flex column that centres its children makes them hug their content, so a
wrapping paragraph inside one collapses to a glyph per line. Everything here
therefore sits in a column of a KNOWN width and centres its text with
textAlign.
THE INK. A 1931 handbill was printed in two inks on cream stock: a warm
black, and one red. The red is spent where the game means it — the name of
the game, the tent's own grin, the words SHE STAYED — and everything else
that wants weight takes velvet or mustard instead. A card that reddens its
section heads has nothing left to say the grin with.
THE SCRIM. A card is paper held up in a tent, not a page in a book: the ring,
the sawdust and the beam stay legible behind it, dimmed and thrown out of
focus rather than blacked out.
roman(n: number) → string
An act number in the roman the tent prints it in.
| arg | type | description |
|---|
| n | number | The act, 1-based. |
withScreen(w: number, h: number, fn: () →
Build cards against a stated frame instead of the live one. Everything inside the callback lays out for that shape; the live frame is back the moment it returns.
| arg | type | description |
|---|
| w | number | The frame width to pretend. |
| h | number | The frame height to pretend. |
| fn | ( | The builder to run. |
examples
local wide = screens.withScreen(1600, 900, function() return screens.title(o) end)
metrics( ) → Metrics
The live frame, and every size the cards derive from it. Call it once per rebuild — never cache it, because the window can change shape under a running game.
twoUp(m: Metrics) → boolean
Whether a card has room to set its blocks in two columns. A wide frame always
does; a tall one does when its column is wide enough that half of it still
holds a readable line.
| arg | type | description |
|---|
| m | Metrics | |
fitSize(text: string, width: number, ideal: number, em: number, tracking: number?) → number
The largest type size at which `text` still fits `width`. Anton is condensed
(~0.44 em a glyph) and EB Garamond is not (~0.48); tracking adds to both.
| arg | type | description |
|---|
| text | string | |
| width | number | |
| ideal | number | |
| em | number | |
| tracking | number? | |
fitSize(text: string, width: number, ideal: number, em: number, tracking: number?) → number
The largest size at which a string still fits a width. The cards use it so a long clown name never runs off the stock.
| arg | type | description |
|---|
| text | string | The string being set. |
| width | number | The width it has to fit. |
| ideal | number | The size to use when it fits. |
| em | number | The face's average glyph width in ems. |
| tracking | number? | Extra per-glyph tracking in ems. |
tentMark(lit: boolean?) → string
The company's big top, drawn as a woodcut. `lit` paints the entrance in sodium — the win — and `dark` leaves it black.
| arg | type | description |
|---|
| lit | boolean? | Whether the doorway is lit. |
rosette( ) → string
The star-and-rule rosette that heads a card.
manicule( ) → string
The pointing hand a handbill puts beside the line it wants read. It marks the focused row.
maniculeDown( ) → string
The same hand turned down the page, for a card standing under it rather than a row beside it.
grinMark( ) → string
The grin the tent leaves behind, in dried greasepaint that has begun to come off: the smile is unequal, the right corner carried higher, and the paint is crazed through with cracks and missing a tooth. The lost card's mark.
ringMark( ) → string
The ring under a single hard lamp — the mark the won card carries.
rule(m: Metrics, tone: string?) → any
The double rule that separates the blocks of a handbill.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| tone | string? | The ink it is struck in. |
eyebrow(m: Metrics, text: string, tone: string?, width: number?) → any
The small-caps line that heads a block.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| text | string | What it says. |
| tone | string? | The ink. |
| width | number? | The column it is set in; the card's own column when omitted. |
para(m: Metrics, text: string, opts: { size: number?, tone: string?, italic: boolean?, align: string?, width: number? }?) → any
A wrapping paragraph, set in the body face inside a column of known width.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| text | string | The paragraph. |
| opts | { size: number?, tone: string?, italic: boolean?, align: string?, width: number? }? | { size, tone, italic, align, width } |
hitPatch(id: string, w: number, h: number) → any
The transparent button laid over a row or a face. A row HAS to carry one: a
container or a label carrying `onClick` lays out and paints and senses
nothing, so a row built that way answers to a pad and a keyboard and is dead
under a mouse and a finger.
| arg | type | description |
|---|
| id | string | |
| w | number | |
| h | number | |
item(m: Metrics, id: string, text: string, note: string?, tone: string?) → any
One line of a menu — the focus target, the click target and the touch target at once. Its ring is what a controller player reads.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| id | string | The item id, matched against Focus.id(). |
| text | string | What it says. |
| note | string? | A dim second line, or nil. |
| tone | string? | The ink it lights up in. |
itemList(m: Metrics, rows: { any }) → any
A stack of menu rows, spaced as one block.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| rows | { any } | The item nodes. |
cap(m: Metrics, text: string, tone: string) → any
One key/button legend, drawn as the cap a player looks for.
| arg | type | description |
|---|
| m | Metrics | |
| text | string | |
| tone | string | |
capBox(m: Metrics, text: string, tone: string) → any
A cap in a box of its own stated width. A bare cap in a flex row takes a
share of the row instead of hugging its glyphs, and the footer comes out as
a stack of full-width bars.
| arg | type | description |
|---|
| m | Metrics | |
| text | string | |
| tone | string | |
familiesFooter(m: Metrics, confirmLegend: string) → any
The footer that says the game answers to a keyboard, a pad and a thumb at once.
| arg | type | description |
|---|
| m | Metrics | The metrics. |
| confirmLegend | string | The legend the CONNECTED pad prints for its south face button. |
stock(m: Metrics, children: { any }, key: string) → any
The handbill itself: cream stock, a heavy outer rule, a hairline inner rule,
and the print texture of a press that was not new in 1931.
| arg | type | description |
|---|
| m | Metrics | |
| children | { any } | |
| key | string | |
root(m: Metrics, key: string, children: { any }, scrim: number?) → any
The frame the stock is pasted on. The ring does not go out behind a card: it
is dimmed and thrown soft, so the beam, the sawdust and whoever is standing
in it are still there behind the paper.
| arg | type | description |
|---|
| m | Metrics | |
| key | string | |
| children | { any } | |
| scrim | number? | |
title(o: CardOpts) → any
The title card: the company, the game's name, the barker's call and the two ways in.
| arg | type | description |
|---|
| o | CardOpts | { confirmLegend, state } |
controlRow(m: Metrics, width: number, name: string, kbm: string, pad: string, touch: string) → any
One row of the control table: the action, then the same action on a
keyboard, on a pad and under a thumb.
| arg | type | description |
|---|
| m | Metrics | |
| width | number | |
| name | string | |
| kbm | string | |
| pad | string | |
| touch | string | |
howto(o: { confirmLegend: string, padLegends: { [string]: string }, state: any }) → any
The how-to-play handbill: what Ada is, what one turn asks, what the tent does between the acts, and every control on all three device families. Every number on it is read from Core, so the card and the game can never disagree.
| arg | type | description |
|---|
| o | { confirmLegend: string, padLegends: { [string]: string }, state: any } | { confirmLegend, padLegends, state } |
block(head: string, kids: { any }) → any
| arg | type | description |
|---|
| head | string | |
| kids | { any } | |
intentWords(step: any) → string
What a clown is about to do, in the order it does it — read off the same
pattern the Director walks, so the warning and the act are one thing.
| arg | type | description |
|---|
| step | any | |
patternWords(id: string?) → string
How a clown's telegraphed pattern reads as a sentence — "guards, then swings for 13, then laughs".
| arg | type | description |
|---|
| id | string? | The clown id. |
rewardFace(m: Metrics, id: string, cardId: string, width: number) → any
One offered card: the handbill face the hand holds, at reward size, with the
focus carried in the gutter around it rather than on the stock. The stock is
the same block a player has been looking at all night, and a lit face that
grew or reddened would be a different card.
| arg | type | description |
|---|
| m | Metrics | |
| id | string | |
| cardId | string | |
| width | number | |
reward(o: { confirmLegend: string, cards: { string }, next: string?, mode: string?, state: any }) → any
The interval between two acts: three faces and a fourth way out. In "take" mode they come off the pool and the fourth row declines them; in "leave" mode they are Ada's own cards and the fourth row keeps every one.
| arg | type | description |
|---|
| o | { confirmLegend: string, cards: { string }, next: string?, mode: string?, state: any } | { confirmLegend, cards, next, mode, state } |
pause(o: CardOpts) → any
The pause card, laid over the held ring.
| arg | type | description |
|---|
| o | CardOpts | { confirmLegend, state } |
stat(caption: string, value: string, tone: string) → any
| arg | type | description |
|---|
| caption | string | |
| value | string | |
| tone | string | |
tally(m: Metrics, st: any) → void
What the night came to, in the tent's own accounting: how often it laughed,
how warm the house got, and who did not get up.
| arg | type | description |
|---|
| m | Metrics | |
| st | any | |
endCard(m: Metrics, key: string, o: CardOpts, spec: { eyebrow: string, head: string, mark: string, markH: number, tone: string, headTone: string, line: string, barker: string }) → any
The two end cards share everything but their mark, their ink and their words.
| arg | type | description |
|---|
| m | Metrics | |
| key | string | |
| o | CardOpts | |
| spec | { eyebrow: string, head: string, mark: string, markH: number, tone: string, headTone: string, line: string, barker: string } | |
won(o: CardOpts) → any
The win card: Ada walked out.
| arg | type | description |
|---|
| o | CardOpts | { confirmLegend, state } |
lost(o: CardOpts) → any
The loss card: the tent painted her.
| arg | type | description |
|---|
| o | CardOpts | { confirmLegend, state } |
itemIds(phase: string, st: any) →
The focusable row ids a phase offers, in the order a stick walks them. The Screens component and the cards agree through this one function.
| arg | type | description |
|---|
| phase | string | The Core phase. |
| st | any | The run state. |
hasCard(phase: string) → boolean
Whether a phase puts a card between the player and the ring.
| arg | type | description |
|---|
| phase | string | The Core phase. |
build(phase: string, o: any) → any
Build the card for a phase. Returns nil in the phases that show the ring alone.
| arg | type | description |
|---|
| phase | string | The Core phase. |
| o | any | { confirmLegend, padLegends, state, barker } |