Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld 6bbe73d1-9…

Screens

Puts every authored screen of *First Light: Lucina* on the glass and drives them. Mounted once, on `mount_screens`; it needs nothing else on that entity.

byclaude-code @ aura·posted 1mo ago
What it does

Screens

Puts every authored screen of First Light: Lucina on the glass and drives them. Mounted once, on mount_screens; it needs nothing else on that entity.

What it draws

ScreenLayerWhat it is
lu-menu960whichever card the phase calls for
lu-hud120the dawn rail, the dose rail, the plate readout, the tray hint, the pause pip and the Keeper's line
lu-speech980the Keeper's line alone, wherever no HUD is carrying it

The HUD is two vertical rails — dawn down the left edge, dose down the right — with the plate's readout above the plate and the pause pip in the top-left corner. Nothing spans the frame: the game films at 1080x1440, and every anchor is a fraction of the live ui.screenSize(), so the same arrangement holds on a television and on a phone held either way.

What decides which card is up

Core.state.phase, which the rules slice's Game.component owns.

PhaseCard
titlethe title card
opening / play / fixingnone — the HUD
pausedthe pause card
resultthe result card: the plate as it was fixed, and its match
won / lostthe two end cards

How to play is an overlay over whichever card opened it, not a phase, so it reads the same from the title card, the pause card and a failed result.

When the player takes a line this component emits uiRequest(action) — "start", "pause", "resume", "restart", "next", "title" — and waits. If nothing has moved the phase within a moment it moves the phase itself, so the game is reachable from its own title card whatever else is loaded. The moment Game answers a uiRequest, that fallback stops firing on its own.

Controls

ui is activated the instant a card goes up and released the instant the last one comes down; it stands down player and uipause while it is live. uipause is live through play alone and carries the one control that opens the pause card.

  • Focus moves on navigate (arrows / d-pad) and navigateStick (left stick), wraps at both ends, and lands on a line the instant a card opens. The focused line carries a brass ring, a lit edge marker and a diamond, and shifts a few points to the right.
  • Choose on confirm (Enter, Space, south), leave on back (Escape, Backspace, right mouse, east); a result or end card also answers restart (R, north).
  • A pointer or a thumb takes a line by touching it, which also moves the ring there, so mixing a mouse and a pad never loses the player's place.
  • Each card carries the legend for the family in the player's hands, read from /runtime/input/device and published as Core.state.device. The instruction page redraws its whole control table for that family.

Public fields

  • menuMap: assetRef(inputMap) — the card scheme. Default ui.
  • pauseMap: assetRef(inputMap) — the pause control. Default uipause.

Methods

  • :repaint() — force the next tick to rebuild every screen. The screens are repainted only when their own inputs change, and a hot reload of theme is not one of those inputs, so this is how an edit to the design system reaches the glass without restarting anything.
  • :focus() — { card, id, device }: the card that is up, the line the ring is on, and the family being drawn for.
  • :setDevice(name?) — pin the family the legends and the instruction page are drawn for; nil hands it back to the device counters.
  • :choose() — take the focused line, as if the player had chosen it.
  • :move(delta) — move the focus ring by delta lines, wrapping.

Lifecycle

  • awake() — paints the first frame.
  • update(dt) / editorUpdate(dt) — one tick either way: reads the device, resolves the card the phase asks for, activates or releases the schemes, and repaints only the screens whose inputs changed.
  • onDestroy() — releases both schemes and unregisters every screen it put up.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

Screens — every authored screen of FIRST LIGHT: LUCINA, and the focus that drives them. Owned by the `screens` slice; mounted once, on `mount_screens`. WHAT IT PUTS ON THE GLASS lu-menu the title card, the Keeper's instruction page, the pause card, the result card and the two end cards — whichever one `Core.state.phase` calls for lu-hud the dawn rail, the dose rail, the plate's readout, the tray hint, the pause pip and the Keeper's line lu-speech the Keeper's line on its own, for the staged beats where no HUD is up — so a spoken line is never lost, only quiet WHAT DECIDES WHICH CARD IS UP `Core.state.phase`, which the `rules` slice's Game.component owns. This component reads a phase and paints it; it never invents one. When the player takes a line it emits `uiRequest(action)` and waits. If nothing has moved the phase within UI_REQUEST_GRACE — which happens only when no other slice is listening for that signal — it moves the phase itself, so the game is reachable from its own title card whatever else is loaded. The moment Game answers a `uiRequest`, that fallback stops firing on its own. INPUT `ui` is activated the instant a card goes up and released the instant the last one comes down; it stands down `player` and `uipause` while it is live, so one Escape cannot close a card and re-open it in the same frame. Focus moves on `navigate` (arrows / d-pad) and `navigateStick` (left stick), wraps at both ends, and lands on a line the instant a card opens. `confirm` takes the focused line, `back` leaves, and a result or end card also answers `restart`. A pointer or a thumb takes a line by touching it, which also moves the ring there, so mixing a mouse and a pad never loses the player's place. `uipause` is live through `play` alone and carries the one control that opens the pause card; the pip on the HUD is the same door for a thumb.

resultMenu( ) → any

The result card is the one whose lines depend on what just happened.

paint(name: string, tree: any, layer: number) → void

Screen plumbing

argtypedescription
namestring
treeany
layernumber

setShown(name: string, visible: boolean) → void

argtypedescription
namestring
visibleboolean

cardForPhase( ) → string

items( ) → any

focusId( ) → string

model( ) → any

The view model every builder reads

request(action: string) → void

Requests. Game.component owns the phase; this asks it to move.

argtypedescription
actionstring

applyFallback(action: string) → void

argtypedescription
actionstring

moveFocus(delta: number) → void

Focus and activation

argtypedescription
deltanumber

openOverlay(name: string) → void

argtypedescription
namestring

closeOverlay( ) → void

activate(id: string?) → void

argtypedescription
idstring?

goBack( ) → void

onNavigate(v: any, dt: number, active: boolean) → void

Input

argtypedescription
vany
dtnumber
activeboolean

unbindMenu( ) → void

bindMenu( ) → void

unbindPause( ) → void

bindPause( ) → void

readDevice(dt: number) → void

Which family is in the player's hands Read from `/runtime/input/device` rather than from `input.observe()`: the document's mapping half costs a frame of work to build and is built while anything reads it, and this needs three counters off the device half.

argtypedescription
dtnumber

count(key: string) → number

argtypedescription
keystring

readPadLabels(dt: number) → void

argtypedescription
dtnumber

q(x: number, n: number) → string

The tick

argtypedescription
xnumber
nnumber

tick(dt: number) → void

argtypedescription
dtnumber

repaint( ) → void

Force the next tick to rebuild every screen from scratch. The screens are repainted only when their own inputs change, and a hot reload of `theme` is not one of those inputs — so this is how an edit to the design system reaches the glass without restarting anything.

focus( ) → void

The card that is up, the line the ring is on, and the family the legends are drawn for.

setDevice(name: string?) → void

Pin the family the legends and the instruction page are drawn for, instead of reading it off the device counters. `nil` hands it back.

argtypedescription
namestring?

choose( ) → void

Take the line the ring is on, as if the player had chosen it. The one call a test needs to drive a card without a device.

move(delta: number) → void

Move the focus ring by `delta` lines, wrapping at both ends.

argtypedescription
deltanumber

awake( ) → void

Lifecycle

update(dt: number) → void

argtypedescription
dtnumber

editorUpdate(dt: number) → void

One tick per frame in every row of the mode/paused table: `update` runs whenever the gameplay clock is running, `editorUpdate` whenever it is held, and routing both to one body unguarded would tick this twice in edit with the clock advancing.

argtypedescription
dtnumber

onCallback(id: string, _data: any) → void

Every control on every card lands here, whatever pressed it. A pointer or a thumb also moves the focus ring to what it touched.

argtypedescription
idstring
_dataany

onDestroy( ) → void

⌬ Metadata
descriptionPuts First Light: Lucina's screens on the glass and drives them: the title card, the Keeper's instruction page, the portrait HUD (dawn rail, dose rail, plate readout, tray hint, pause pip, subtitle strip), the pause card, the result card with the exposed plate, and the two end cards, chosen from Core.state.phase. Owns controller focus navigation for every card - a visible brass ring, wrap-around, something focused the instant a card opens - and the `ui` scheme that stands the night's controls down while a card is up. Every card is drivable by keyboard, by pad and by thumb, and lays out from ui.screenSize() so it holds at 3:4 and at 16:9.

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

19+items
▣
module · born here
❒asset
# hud The heads-up display of *First Light: Lucina*, as a tree. It takes the view model `Screens.component` builds and returns a widget tree; it registers nothing and reads no control. - `H.hud(m)` — the whole HUD. - `H.speech(m)` — the Keeper's line on its own, for the staged beats where no HUD is up, so a spoken line is never lost, only quiet. ## The arrangement, and why it is this one The game films at 1080x1440, so horizontal room is the scarce axis and nothing may span the frame: - the **dawn rail** down the LEFT edge, under a drawn window whose sun climbs with it; - the **dose rail** down the RIGHT, betafite green until the warning mark and rust past it, breathing once it is hot; - the **plate readout** above the plate in the middle of the frame — which plate this is, what it is called, and how much of it currently reads right; - the **tray hint** low, naming the shape in the hands and how many are left; - the **pause pip** in the top-LEFT corner, because the engine's own touch surface owns the right of the screen and a thumb aimed at a pip there is read as a look-drag; - the **Keeper's line** along the foot, whether or not the audio exists. Every one of them is anchored as a fraction of the live screen. On a touch device the rails are cut short, because the thumb owns the bottom of the frame. Past the dose warning the frame itself goes hot at the edges — four thin bars rather than one full-screen box, so nothing the player has to point at ends up underneath it.
▲ 0↑ born
▣
module · born here
❒asset
# cards The authored cards of *First Light: Lucina*, as trees. Every function takes the view model `Screens.component` builds and returns a widget tree; nothing here registers a screen, reads a control or touches the world. - `S.title(m)` — the lens of Lucina, the name, the premise, and the way in. - `S.howto(m)` — the Keeper's written instruction, on a logbook page: the exposure diagram, what a night is, and the control table for the family in the player's hands. The paper scrolls; the line that closes it is pinned under the fold at thumb height, so it is never past the bottom edge. - `S.pause(m)` — the clock held: dawn, dose and match, and four ways on. - `S.result(m)` — the plate as it was fixed, drawn cell by cell, with the match against what it needed. - `S.won(m)` / `S.lost(m)` — the two end cards, with the ledger of five plates or the dose that ended the night. - `S.card(m)` — the switchboard; an unknown card is a zero-sized box. Each card is ONE centred column inside a scrolling shell, because the game films at 1080x1440 and a tall frame has no room for two. Only the logbook page folds into two columns, and only when the frame is wide enough to hold them. The shell is what makes a card safe at any shape: the column is a full screen tall and centres what it holds, so a card that fits sits in the middle and a card that does not scrolls rather than losing its last line.
▲ 0↑ born
▣
module · born here
❒asset
# game.core The one table every slice of *First Light: Lucina* reads. ```luau local Core = require("game.core") ``` | What | Where | |---|---| | The plate grid, cell ↔ world conversions | `Core.GRID`, `Core.PLATE`, `Core.cellToWorld`, `Core.worldToCell`, `Core.cellIndex` | | The five piece shapes and the placement rules | `Core.SHAPES`, `Core.SHAPE_ORDER`, `Core.footprint`, `Core.canPlace`, `Core.pieceAt` | | The exposure and dose numbers | `Core.EXPOSURE`, `Core.DOSE` | | The phases and the level shape | `Core.PHASES`, `Core.LEVEL_COUNT`, the `Level` type | | The live game state, held across hot reloads | `Core.state` — the `State` type says which slice writes each field | | The signal bus | `Core.on(name, fn)`, `Core.off`, `Core.emit(name, ...)` — the signal list and its senders are in the source | | The three shorthands | `Core.request(action)`, `Core.sfx(name, opts?)`, `Core.say(lineId)` | | Which component mounts where | `Core.MOUNTS`, `Core.mount()` — the scene entrypoint calls it on every load | Subscribe in `awake`, subscribe again from `onModuleReload` (a duplicate is dropped), release in `onDestroy`. The rules of the game are stated in `design/spec.md`; this module carries the numbers and the shapes they share.
▲ 0↑ born
▣
module · born here
❒asset
# theme The design system every authored screen of *First Light: Lucina* is drawn from. It builds widget nodes and returns them: it registers no screen, reads no phase and spawns nothing. ## What is in it - **Type** — three faces: `DISPLAY` (EB Garamond, the lighthouse's own lettering), `BODY` (PT Serif, the Keeper's logbook) and `MONO` (Berkeley Mono, every number on the glass), each with real fallbacks behind it. - **Colour** — `C`, the whole palette: near-black basalt and iron, brass as the material the interface itself is made of, and the two accent lights the art direction allows — the betafite's green-gold and the dawn. - **Measure** — `u(f)` is `f` of the SHORT axis, `wf`/`hf` fractions of the width and the height, `T` the type scale in short-axis fractions with `TMIN` as a physical floor, `TAP` the smallest side of anything a thumb presses, and `cardWidth()` the reading column. `previewSize` lays a tree out against a stated shape instead of the live screen, which is how a set of screens composed in a 3:4 frame is proved to hold on a 16:9 one. - **Atoms** — `text`, `num`, `display`, `para`, `eyebrow`, `spacer`. - **Motion** — `entrance(delay, fromY)` and `breathe(from, to, seconds)`. - **Ornament** — `svg`, `diamond`, `rule`. - **Plates** — `panel` (the dark card), `page` (the logbook paper), `scrim`. - **Controls** — `item` (the one control shape every card uses, with the focus ring a controller needs), `cap` / `capInk` (a keycap, a pad button, a touch target) and `noteChip`. - **Meters** — `dawnRail` (the sky rising, four keys) and `doseRail` (betafite green until the warning mark, rust past it). - **The plate** — `plateFigure(cells, width)`: the 7x10 plate as it stands, clear glass pale and exposure driving it to black, a brass outline on every cell the target asked to be dark and a rust ring on every cell that reads wrong. The result card's whole argument. - **Marks** — `lensMark`, `crystalMark`, `frisketMark`, `pauseMark`, `dawnMark`, `tubeMark`, `diagramMark`, each sized from the screen. Every size is a fraction of the live screen. No pixel constant is written here apart from the physical floors, so one tree composes at 1080x1440, at 1920x1080 and on a phone held either way. `theme.live` is the one table that outlives a hot reload: a component's module-level locals belong to the chunk that declared them, so the claim on an input scheme is stamped here where the next chunk can find it.
▲ 0↑ born
backing path · game/screens/Screens.component

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.