Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld 8319a68f-5…

Screens

Everything *First Light: Henter of the Glew* puts on the glass, and the console a player drives it with. One instance, on `mount_screens`; it needs nothing else on that entity.

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

Screens

Everything First Light: Henter of the Glew puts on the glass, and the console a player drives it with. One instance, on mount_screens; it needs nothing else on that entity.

ScreenLayerWhat it is
fl_hud20the lantern rail over the dawn rail, and the best-night plate
fl_caption40the written form of whatever is being spoken
fl_card1000title, how to hent, pause, and the end card in four readings

What it drives

  • title → howto → intro, and straight from either to the hill.
  • Pause and resume, restart, and leaving the hill for the title.
  • The end card, once the phase is won / lost and Core.state.cutscene is false — so the resolution shot is never covered by it.
  • The caption band, from the line event, held for a reading time taken from the length of the line.

The HUD stands while the round is in the player's hands and goes while a cutscene has the camera. The caption band outlives it, because a line spoken over a cutscene is still read; it stands down entirely while another slice is already carrying the line on a screen of its own, so a line is never printed twice.

The three families

MenuOn the hill
Mouse + keyboardarrows / WASD walk the focus, Enter or Space confirms, Escape backs out, R restarts; a click lands on the rowgame.henter
Toucha tap lands on the row itself, which is at least L.touch points tallthe on-screen stick, Hent and Pause the game.henter map declares
Controllerthe left stick and the d-pad walk the focus ring, south confirms, east backs out, north restartsgame.henter

The menu scheme is game.vendor.menu, activated while a card is up and stood down the moment it goes. It suppresses the henter and player groups, so the Escape that paused the round resumes it and the touch overlay's own buttons leave the screen while a card is on it. Focus lives in game.vendor.Focus; a pointer crossing a row re-points it, so the ring and the cursor never disagree about what Enter would press.

Prompts name the connected pad's own buttons through Zin.gamepad.label, so an Xbox pad reads A and a PlayStation pad reads Cross.

Public

menuMapthe card scheme (game.vendor.menu)
clickClipthe sound a card control makes when it answers
captionSecondsthe caption's base hold, before the per-character reading time
:card()the card on the glass right now, or nil
:focused()the row the focus ring is on — what confirm acts upon

Interface

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

conforms to

zero/source-extract/v2

strict

mag2(v: { x: number, y: number }) → number

argtypedescription
v{ x: number, y: number }

catch(v: { x: number, y: number }) → void

argtypedescription
v{ x: number, y: number }

click(volume: number, pitch: number) → void

argtypedescription
volumenumber
pitchnumber

cardKind( ) → string

The card a phase asks for, or nil when the hill is the whole screen.

touchCeiling(h: number) → number

The lowest point the touch overlay has claimed, so a caption never lands under the Hent button on a phone. On a desktop nothing is claimed and the band sits where the layout put it.

argtypedescription
hnumber

anotherCaptionIsUp( ) → boolean

A spoken line belongs on the glass ONCE. Another slice may put the line it is speaking up itself while it holds the camera; when a screen of its own is already carrying one, this band stands down rather than printing the same words twice, and comes back the moment that screen goes.

padNames( ) → void

name(button: string, fallback: string) → string

argtypedescription
buttonstring
fallbackstring

stripEntrances(node: any) → void

An entrance is a one-shot: it plays on the build that introduces a screen and is stripped from every rebuild after, so a focus move or a changed count does not replay the whole cascade. An infinite idle loop is kept.

argtypedescription
nodeany

playerT( ) → number

Where the Henter stands between the foot of the hill and its summit, so the dawn rail can draw the player under the light-line coming down at them.

viewModel(L: T.Layout) → Hud

argtypedescription
LT.Layout

cardVm( ) → Cards

promptsFor(kind: string, pad: { [string]: string }) → void

argtypedescription
kindstring
pad{ [string]: string }

act(id: string) → void

argtypedescription
idstring

focusedId( ) → string

settled( ) → boolean

A card does not answer a key that was already down when it opened. Escape both pauses the round and backs out of the card it opens, so without this a single press could pause and resume in the same breath.

confirm( ) → void

back( ) → void

restart( ) → void

armMenu( ) → void

Subscriptions are stamped with the activation that made them, so a handler left over from an earlier card is inert whichever way the binding layer treats a deactivate.

disarmMenu( ) → void

buildCard(kind: string, L: T.Layout, entrance: boolean) → any

argtypedescription
kindstring
LT.Layout
entranceboolean

ensureRegistered( ) → void

refresh(dt: number) → void

argtypedescription
dtnumber

awake( ) → void

onDestroy( ) → void

update(dt: number) → void

argtypedescription
dtnumber

editorUpdate(dt: number) → void

argtypedescription
dtnumber

card( ) → string

Whether a card is on the glass right now, and which one.

focused( ) → string

The row the focus ring is on, which is what `confirm` acts upon.

onCallback(id: string) → void

argtypedescription
idstring
⌬ Metadata
descriptionPuts every authored screen of First Light: Henter of the Glew on the glass and drives it — the HUD, the caption band, and the title, how-to, pause and end cards — reachable in full from mouse and keyboard, from touch, and from a controller with a visible focus ring.

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.

26+items
▣
module · pulled in
❒asset
# 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.
▲ 1↓ import
▣
module · born here
❒asset
# core <!-- zero:scaffolded-readme --> <!-- Replace the line below with what this module provides and who consumes it, then delete both comment lines. --> TODO: describe this module.
▲ 0↑ born
▣
module · born here
❒asset
# theme The palette, the type scale and the layout metrics every screen of *First Light: Henter of the Glew* is measured from. ```lua local T = require("game.ui.theme") local L = T.layout() -- read it fresh every frame local W = T.layout({ width = 1920, height = 1080 }) -- ask about another shape ``` `T.layout()` derives every size from the LIVE `ui.screenSize()`: one scale factor `s` taken from the short edge, a card width capped by both edges, a touch target no smaller than a thumb, and `compact` / `dense` for a screen with more width than height. Nothing here is a number chosen for one frame, which is what lets the same screens read on a phone held either way, a laptop, and a television. | | | |---|---| | `T.color` / `T.wash` | night blue, violet, mist, glew amber, ember, bone — solid and translucent | | `T.font` | `display` (EB Garamond) for the folk-tale voice, `body` (PT Serif) for what a player reads at speed | | `T.layout(size?)` | the metrics: `s`, `pad`, `railW`, `cardW`, `touch`, `type.*`, `compact`, `dense` | | `T.label` / `T.svg` / `T.abs` | the node shapes every screen repeats | | `T.enter` / `T.breathe` | a one-shot entrance and an idle glow | Consumed by `game.ui.hud`, `game.ui.cards` and `game.ui.Screens`.
▲ 0↑ born
▣
module · born here
❒asset
# hud The readout the hill is played under, and the caption band that carries a spoken line as written text. ```lua local Hud = require("game.ui.hud") ui.registerScreen("fl_hud", Hud.tree(vm, L), 20) ui.registerScreen("fl_caption", Hud.caption(vm, L), 40) ``` Both builders are **pure**: they take the view model `game.ui.Screens` builds each frame and the live `game.ui.theme` layout, and return a tree. They read no game state and register nothing. ## What it draws **The left rail**, because the filmed screen is taller than it is wide and nothing may span the width. Reading down: - the **lantern** — a glass phial filling with what has been hented, notched at bronze / silver / gold, the count under it in the tier's own colour, and the next mark named; - the **dawn rail** — the hill from the summit at the top to the foot at the bottom, the light-line walking down it, the Henter's own place marked beneath, and the time till first light, which turns ember in the last of the round. Opposite it sits the **best night** plate, and past `dawn = 0.72` a warm wash arrives at the top of the frame — the clock made into a picture, costing no horizontal room. **The caption band** prints whatever is being spoken. Every voiced line ships with its written form, so a failed generation, a muted player and a noisy room all still get the line. It sits above whatever the on-screen touch overlay has claimed (`vm.touchCeiling`), so a caption never lands under the Hent button.
▲ 0↑ born
▣
module · born here
❒asset
# game.audio.sfx Play a one-shot from anywhere on the hill. ```lua local Sfx = require("game.audio.sfx") Sfx.play(clipRef) -- flat, non-spatial Sfx.play(clipRef, { pos = { x, y, z }, volume = 0.7 }) -- out on the hill Sfx.play(clipRef, { pitch = 1, jitter = 0.12 }) -- a run that is not a machine Sfx.ui("confirm") -- the menu's own set ``` It spawns a temporary entity carrying an `Audio` component, lets it sound, and despawns it after `ttl` seconds. Pass `pos` for a spatial source, heard from the `AudioListener` on the CameraRig. Resolve your clip through a **literal** identity so the reference is pinned when this world is installed elsewhere. ## Three things every caller gets **A voice budget.** Below a reserve of free voices a low-priority one-shot is dropped rather than stealing the narrator's. `priority` 0 is set dressing, 1 is the default, 2 survives a crowded mix, 3 always plays. **A collapse window.** Ten motes caught in one frame is one sound, not ten stacked copies 6 dB louder than anything else in the mix. The same clip asked for again inside `collapse` seconds (35 ms by default) is dropped. Pass a longer window for a sound that should never double up, or `priority = 3` to opt out. **Pitch jitter.** `jitter` spreads repeats of one clip either side of their pitch, so a run of them reads as a hill rather than a loop. ## Options `pos`, `volume`, `pitch`, `jitter`, `maxDistance`, `channel`, `ttl`, `name`, `priority`, `collapse`, `looping`. `channel` is one of `sfx` / `ui` / `ambient` / `music` / `voice` — the names the mix knows. A source naming anything else plays at unity, which is a sound nobody can turn down, so an unknown name falls back to `sfx`. ## The menu's set `Sfx.ui(name)` plays one of `click` / `move` / `confirm` / `back` / `deny` — small struck-glass sounds, because the lantern is glass and brass and so is the menu. Non-spatial, on the `ui` channel, and high enough priority to get through a crowded mix. `Sfx.stopAll()` drops every one-shot still sounding, so a hent that started a frame before the round ended does not carry into the end card.
▲ 0↑ born
▣
module · born here
❒asset
# cards Every screen *First Light: Henter of the Glew* puts BETWEEN the player and the hill. ```lua local C = require("game.ui.cards") ui.registerScreen("fl_card", C.title(vm, L, opts), 1000) ``` | Builder | Card | Rows | |---|---|---| | `C.title(vm, L, opts)` | the crest, the name, the tagline, the best night | `to_hill`, `to_howto` | | `C.howto(vm, L, opts)` | glew, the hent, hoggies, the light-line, the tiers, and every way to play | `to_hill`, `to_title` | | `C.pause(vm, L, opts)` | where the round stands: lantern, time left, best | `resume`, `again`, `to_title` | | `C.ending(vm, L, opts)` | the stamp, the count, the word, the tiers | `again`, `to_title` | Every builder is **pure**: it takes the view model, the live `game.ui.theme` layout, and `opts` — which row holds the focus, what the connected pad's buttons are called, and the prompt strip to print — and returns a tree. Nothing here registers, shows, or writes game state; `game.ui.Screens` does all of that. ## The three families, in the card itself A row is at least `L.touch` points tall and carries its own `onClick`, so a pointer click, a finger tap and a pad's `confirm` on the lit row all land on the same node. Exactly one row wears the **focus ring** — an amber left bar, a lit border and a coloured glow — which is the controller's cursor. The how-to card prints the keyboard, controller and touch bindings side by side, and every card's footer names the keys, the pad buttons and the tap for what it offers. ## Shape `L.compact` (a screen with more width than height) lays the how-to card's three rules ACROSS instead of down, and `L.dense` tightens the vertical rhythm, so the same card fits a 3:4 phone, a 16:9 laptop and a phone held sideways without clipping or shrinking the type below reading size.
▲ 0↑ born
backing path · game/ui/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.