# Focus
Menu focus navigation for the cards of GREASEPAINT. A controller has no
pointer, so every menu carries an ordered ring of focusable ids and exactly one
of them is lit; this module walks that ring.
- `Focus.open(list, prefer)` — open a menu: the ids in stick order, and which starts lit.
- `Focus.axis(v, dt)` — feed the `navigate` axis; one flick is one step, a held stick repeats.
- `Focus.point(id)` — a mouse crossing a row or a finger tapping it moves the focus there.
- `Focus.id()` / `Focus.isFocused(id)` — read it.
- `Focus.close()` — nothing is focused until the next `open`.
- `Focus.release()` — let go of a held direction, so a stick held through a transition does not keep walking.
The ring is whatever the card offers, so an interval with three faces and a
fourth row is a ring of four and needs nothing said about it here: `open` takes
the list `screens.itemIds` returns for the phase.
A held stick repeats on a delay measured in seconds AND in frames. On a machine
running long frames a single flick can be one frame carrying half a second of
`dt`, and a delay measured only in time turns that flick into two steps.
Fed by `game/input/console.inputMap`; consumed by `game.ui.Screens`, which asks
it which row to draw lit.
# hud
The instruments CINNAMON is driven by.
```lua
local Hud = require("game.screens.hud")
ui.registerScreen("ns_hud", Hud.build(St.metrics(), rules.ledger(), true, caption), 20)
```
Two rails flank the play area, because the run is vertical and so is the screen
it is filmed on:
- **LIGHT**, on the left, is where the rover stands against the dawn line —
hot at the bottom, cold at the top, the copper band bracketed between them,
with the rover's marker riding it. Its gradient stops are cut at the same
distances `NS.bandAt` cuts the bands, so the marker and the colour under it
always agree.
- **ROUTE**, on the right, is how far up the corridor the rover has come, with
Peary Station's dome at the top of it.
The middle of the frame is left to the game. Under it sit the two things that
can end a run: the cargo's temperature, filled left from centre in frost and
right in ember, and the xylidine, with the number of hops it still buys. A
caption sits above them, and a band alarm washes the frame's edge when the
cargo is cooking or freezing.
`build(m, ledger, live, caption, bare)` takes the metrics, a rules ledger (an
empty table is fine — the instruments read STANDBY), whether a run is under
way, the narration line on screen, and `bare`, which draws the caption alone
for the opening, when the frame belongs to the camera.
Every meter is laid out with flex weights rather than positions, so the same
tree reads at 1080x1440, on a phone, and across a television. The bottom block
clears the bottom tenth of the frame, where a cutscene's letterbox bar and a
phone's home indicator land.
# console
The controls a CARD answers to: the title, the how-to-play handbill, the
interval between two acts, the pause card and the two end cards.
It is live exactly while a card is up — `game.ui.Screens` activates it the
instant one goes up and releases it the instant the last one comes down. Its
group is `ui`, and it stands down `player` and `table`, so nothing behind a card
answers while the card has the player.
| Control | Keyboard | Pad | Touch |
|---|---|---|---|
| `navigate` | Arrows or WASD | Left stick | — the rows |
| `confirm` | Space or Enter | South (A / Cross) | — the rows |
| `back` | Backspace | East (B / Circle) | — the row that says so |
| `resume` | Escape | Start | — the Resume row |
`navigate` binds each direction to two keys at once — the WASD key and the arrow
key beside it — because a player reaching for a menu uses whichever is already
under their hand.
## Why no touch class
Every row of every card is a button under a finger: tapping one moves the focus
there and acts on it in the same gesture. A finger does not walk a menu to reach
a row and then confirm it — it puts itself on the row. So each of these four
declares `touch = false` rather than adding an on-screen stick and three
unlabelled circles that would each be a second, worse way to do what the card
already is. Backing out is the how-to's Back row and the pause card's Resume row;
leaving the table is the pause glyph the Table draws.
# ns
The shared contract of *First Light: Nearside*. Every slice of the game
requires this module and nothing else of each other's.
```luau
local NS = require("game.ns")
```
## What it carries
- **Constants** — `NS.ROUTE` (the corridor: `startZ`, `finishZ`, `halfWidth`),
`NS.DAWN` (the terminator's start, speed and the hot/cold distances),
`NS.CARGO`, `NS.FUEL`, `NS.ROVER`, `NS.PALETTE`, `NS.AUDIO`, `NS.NAMES`
(the entity names the scene authors) and `NS.COMPONENTS` (the component
identities the entrypoint mounts).
- **The light** — `NS.lightAt(dz)` and `NS.bandAt(dz)` read the sunlight at a
distance from the dawn line, `dz = z - terminatorZ`. The rules and the
lighting both read these, so the meter and the picture agree.
- **The screen** — `NS.screen()` and `NS.unit(fraction)`: every layout is
stated against the live screen size, never in pixels chosen for one frame.
- **Events** — `NS.on(name, fn)` / `NS.emit(name, payload)`; the names are in
`NS.EVENTS`.
- **Phases** — `NS.phase()`, `NS.setPhase(p)`, `NS.resume()`, `NS.tick(dt)`,
`NS.runTime()`. Phases: `title → howto → intro → playing ⇄ paused → won | lost`.
- **Surfaces** — `NS.provide(name, table)` / `NS.get(name)` /
`NS.whenProvided(name, fn)`. The shapes are in `/zero/source/design/spec.md`.
- **Maths** — `clamp`, `lerp`, `smooth`, `approach`, `hash`, `rgb`.
## Notes
- Module-local state (phase, surfaces, handlers) resets on hot reload of
this file; components re-provide their surfaces in `awake`.
- The route runs north, +Z. South of the terminator is sunlight.
# rover
Driving CINNAMON: throttle and steering, the xylidine hop, and pause.
Three controls, each carrying keyboard, gamepad and touch, so a run is
completable with any one family alone:
| Control | Keyboard / mouse | Gamepad | Touch |
|---|---|---|---|
| `drive` (axis2, +y north) | WASD | left stick | on-screen stick, left zone |
| `hop` (button) | Space, left mouse | south (A / Cross) | **HOP**, lower right, large |
| `pause` (button) | Escape, P | start | **Pause**, upper right, small |
The map is in the `rover` group and stands the `player` group down while it is
live, so the walk scheme the avatar brings answers to nothing behind the wheel.
The screens slice's `console` map names `rover` in turn, so the whole set goes
quiet while a card is up and comes back when it closes.
`RoverDrive` activates this while the phase is `playing` or `paused`, and
releases it otherwise — a title card is not a place for a throttle.
# cards
The title, how-to, pause and end cards of *First Light: Nearside*.
```lua
local Cards = require("game.screens.cards")
local tree = Cards.title(St.metrics(), { focus = Focus.id(), padLabel = "A",
captions = true, ledger = rules.ledger() })
```
One shape serves all four: a scrim over the game, a panel column centred in it,
art at the top, a heading, a stack of rows, and the control legend at the foot.
The column is measured in `game.screens.style`'s metrics, so it fills a phone
held upright, sits comfortably in the 3:4 frame this game is filmed at, and
stops growing on a television instead of spanning it.
Every row is three things at once: a tap target carrying its own `onClick`, a
stop on the focus ring a stick walks, and a click target for a mouse. `focus`
in the context is the id currently lit, and the row that holds it wears the
ring.
| | Rows |
|---|---|
| `title(m, ctx)` | `start` · `howto` · `captions` |
| `howto(m, ctx)` | `howto_back` |
| `pause(m, ctx)` | `resume` · `restart` · `quit` |
| `ended(m, ctx, won)` | `restart` · `quit` |
`items(kind)` is the focus ring for a card kind, in the order its rows are
drawn — the component opens the ring from it, so a card and its ring cannot
disagree about what is reachable.
# style
The one place the screens of *First Light: Nearside* agree on colour, type and
measure.
```lua
local St = require("game.screens.style")
local m = St.metrics()
{ type = "label", props = { text = "XYLIDINE" }, style = St.caption(m) }
```
## Measure
`metrics()` returns every length a screen tree needs, derived from the live
screen size — or from a size you hand it, which is how a layout is checked at a
shape the engine is not currently running at:
```lua
St.metrics({ w = 1920, h = 1080 })
```
Lengths come from `unit`, the smaller of the screen's short side and three
quarters of its height. A card is a column, so what limits it on a wide screen
is the height: 1080x1440 and 1920x1080 share a short side and have very
different room to stack in. Every size carries a cap, so type stops growing on
a large screen, and a floor, so words stay words on a phone held sideways.
## Type
Four faces, each with a job: `ArchivoBlack` for the game's name, `Anton` for
headings and instrument labels, `Arimo` for prose, `BerkeleyMono` for anything
a player reads as a number. `title` / `heading` / `caption` / `text` /
`readout` return fresh style tables, so a caller may add to what it gets back.
`fit(family, text, track, avail, cap)` answers the largest size at which a line
still fits the width it has. It estimates from the face's average advance, so
it is there to **shrink** a line that would run past its box, never to choose a
size on its own — every call passes the size it wants as the cap.
## Surface
`panel(m)` is the raised card and instrument tile: a lit top edge over a step
in lightness, then the shadow. `well(m)` is the recess every meter is filled
inside. `row(m, focused)` is a card row and the focus ring it wears when a pad
walks onto it. `enter(delay, lift)` and `pulse(period, from, to)` are the two
animations the screens use — an entrance that staggers across siblings, and a
slow breath for a live instrument.
Colours are `NS.PALETTE` plus the UI's own mixes of it; `BAND` maps each of the
four light bands to the colour every readout paints it in.
# back
The 'Back' control.
# confirm
The 'Confirm' control.
# navigate
Walking the rows of a card: one flick is one step, a held stick repeats.
Each keyboard direction answers to TWO keys — the WASD key and the arrow key
beside it — because a player reaching for a menu uses whichever is already under
their hand. An axis takes one binding per device class, so each direction is
itself a vector of the two keys that mean it.
On a pad it is the left stick; under a thumb it is the on-screen stick in the
left zone.
# resume
The 'Resume' control.
# drive
The 'Drive' control.
# pause
The 'Pause' control.