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

Screens

Puts every authored screen of *First Light: Vagant* on the glass, and drives them. It is mounted once, on `mount_screens`, and needs nothing else on that entity.

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

Screens

Puts every authored screen of First Light: Vagant on the glass, and drives them. It is mounted once, on mount_screens, and needs nothing else on that entity.

What it draws

ScreenLayerWhat it is
fl-menu960whichever card the phase calls for
fl-hud120dawn band, clock, pause pip, the seven standards, oil, wig
fl-alert118the hunt vignette and the relight prompt
fl-say980the subtitle line, above the cards

The subtitle sits above the cards on purpose: a staged ending reads you your entry in the minute-book while the card is on the glass, and a line nobody can read is a line the game did not say.

What decides which card is up

fl.state.phase, which the loop slice owns.

PhaseCard
boot, titlethe title card
openingnone — a staged shot, with only the words over it
playnone — the HUD
pausedthe pause card
won / lostfirst light, or the entry in the minute-book

How to play is an overlay over whichever card opened it, not a phase.

When the player takes a line, this component emits uiRequest(action) — "start", "pause", "resume", "restart", "title" — and waits. If nothing has moved the phase after a moment it moves the phase itself, so the game is reachable from its own title card whatever else is or is not listening.

Controls

ui.uimenu is activated the instant a card goes up and released the instant the last comes down; it stands the player's own scheme down while it is live. ui.uipause carries the one control that has to stay live during play.

  • Focus moves on navigate (arrow keys / d-pad) and navigateStick (left stick), wraps at both ends, and lands on something the instant a card opens. The focused line carries a brass ring, a lit edge marker and a diamond.
  • Choose on confirm (Enter, Space, south), leave on back (Escape, right mouse, east), and an end card also answers restart (R, north).
  • A pointer or a thumb takes a line by touching it. Doing so moves the ring to that line, so a player who mixes a mouse and a pad never loses their place.
  • Each card shows the legend for the family actually in the player's hands, read from /runtime/input/device.

Public fields

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

Methods

  • :repaint() — force the next tick to rebuild every screen. The screens are only repainted when their own inputs change, and a hot reload of a style module is not one of those inputs, so this is how an edit to ui.flui reaches the glass without restarting anything.

Lifecycle

  • awake() — subscribes to line, caught and dawn, activates the pause control, and paints the first frame.
  • update(dt) / editorUpdate(dt) — one tick either way: reads the device, ages the subtitle, resolves the card the phase asks for, and repaints only the screens whose inputs changed.
  • onDestroy() — drops every subscription, 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: VAGANT, and the focus that drives them. Owned by the `screens` slice. WHAT IT PUTS ON THE GLASS fl-menu the title card, the how-to-play broadsheet, the pause card and the two end cards — whichever one `fl.state.phase` calls for fl-hud the dawn band, the clock, the pause pip, the seven standards, the oil gauge and the wig chip fl-alert the hunt vignette and the relight prompt (the looping motion, on its own screen so a HUD refresh never restarts it) fl-say the subtitle line — every spoken line, in writing, always WHAT DECIDES WHICH CARD IS UP `fl.state.phase`, which the `loop` slice owns. This component never invents a phase; it reads one and paints it. When the player takes a line on a card it emits `uiRequest(action)` and waits. If nothing has moved the phase after UI_REQUEST_GRACE seconds — because no other slice listens for that signal yet — it moves the phase itself so the game is still reachable from its own title card. The moment `loop` answers `uiRequest`, that fallback stops firing on its own; nothing needs to be removed. INPUT `ui.uimenu` is activated the instant a card goes up and released the instant the last one comes down. It suppresses the `player` group, so the vagant does not walk while a menu is open, and `uipause`, so one Escape cannot close a card and re-open it in the same frame. Focus moves on `navigate` (d-pad) and `navigateStick` (left stick), takes on `confirm`, leaves on `back`, and an end card also answers `restart`. A pointer clicks the control it is over; a finger taps it. Every control is a real target at least u(0.070) on its short side.

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

Screen plumbing

argtypedescription
namestring
tree?
layernumber

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

argtypedescription
namestring
visibleboolean

cardForPhase( ) → string

What card the phase asks for

items( ) → void

focusId( ) → string

model( ) → void

The view model every builder reads

request(action: string) → void

Requests. The loop slice owns the phase; this asks it to move.

argtypedescription
actionstring

applyFallback(action: string) → void

The self-disabling fallback. It runs only when a request went unanswered, which is only when nothing else in the world listens for `uiRequest` yet.

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: ?, dt: number, active: boolean) → void

Input

argtypedescription
v?
dtnumber
activeboolean

unbindMenu( ) → void

bindMenu( ) → void

bindPause( ) → void

readPlayerPos( ) → void

Reads that keep the HUD honest

readDevice( ) → void

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 is reading it, and this needs two numbers off the device half.

promptIndex( ) → number

wire( ) → void

The tick Take over the signal bus from whatever chunk held it. Registered from the tick rather than from `awake`, because reloading a component's source reconciles the live instance in place without running `awake` again — a subscription made there would go on writing into the previous chunk's locals while this one reads its own, and every spoken line would vanish.

tick(dt: number) → void

argtypedescription
dtnumber

repaint( ) → void

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

awake( ) → void

update(dt: number) → void

argtypedescription
dtnumber

editorUpdate(dt: number) → void

argtypedescription
dtnumber

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

Every control on every card lands here, whatever pressed it.

argtypedescription
idstring
_dataany

onDestroy( ) → void

⌬ Metadata
descriptionPuts First Light: Vagant's screens on the glass and drives them: the title card, the how-to broadsheet, the portrait HUD, the alert layer, the subtitle line, the pause card and the two end cards, chosen from fl.state.phase. Owns controller focus navigation for every menu — a visible brass ring, wrap-around, something focused the instant a card opens — and the `ui` scheme that stands the player's controls down while a card is up. Every card is drivable by keyboard, by pad and by thumb.

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.

15+items
▣
module · born here
❒asset
# flscreens Every screen of *First Light: Vagant*, as a widget tree. Each function is a pure builder: it takes a view model and returns a tree. It registers nothing, shows nothing, and writes no game state — `Screens.component` does all of that. ```luau local S = require("ui.flscreens") ui.registerScreen("fl-menu", S.title(model), 960) ``` ## The builders | Call | What it draws | |---|---| | `S.title(m)` | the title card: the mark, the name, the logline, the menu | | `S.howto(m)` | the how-to-play broadsheet and its control table | | `S.hud(m)` | dawn band, clock, pause pip, the seven standards, oil, wig | | `S.alert(m)` | the hunt vignette and the relight prompt | | `S.say(m)` | the subtitle line | | `S.pause(m)` | the pause card, with the night's figures on it | | `S.endCard(m)` | first light, or the entry in the minute-book | ## The view model ```luau { device = "kbm" | "pad" | "touch", -- which family is holding it focus = "fl:begin", -- the id carrying the focus ring items = { { id, label, hint?, danger? }, ... }, backId = "fl:back", line = { text = "...", speaker = "The Vagant" } | nil, playerPos = { x, y, z } | nil, -- for the relight prompt won = boolean, reason = "caught" | "dawn", where = "Middle Ditch", } ``` Everything else — the clock, the lamps, the oil, the wig, whether you are being hunted — is read live from `fl.state` and `fl.lamps`. ## Why the HUD is three screens `S.hud` carries the gauges and is rebuilt about once a second, whenever a figure on it changes. `S.alert` and `S.say` are separate because they are the parts that carry **looping and entrance motion**, and re-registering a screen restarts every animation on it — so they are rebuilt only when being hunted, standing at a dark lamp, or the spoken line actually changes. ## Portrait first The filmed frame is 3:4 and the portrait arrangement is the default: the dawn band along the very top, the standards down the left, the oil up the right, the words low and centred, nothing spanning the width. `flui.portrait()` picks the wide variant where a wide screen needs one — a narrower menu column, a smaller title mark, and less vertical padding, because a wide screen has to give its vertical room back to the stack.
▲ 0↑ born
▣
module · born here
❒asset
# fl One-paragraph description of what this module provides and who consumes it. ## Exports - `M.exampleFn(arg) -> result` — what it does and when to call it. - `M.exampleValue: type` — what value it carries. ## Usage Replace `<identity>` with the string `asset.create` returned — this module's source path with `/` written as `.` and each segment's type suffix dropped, the way a module at `/source/lib/thing.module` is `lib.thing` and one at `/source/kit.package/thing.module` is `kit.thing`. ```luau local M = require("<identity>") M.exampleFn("foo") ``` ## Notes - Anything callers need to know about THIS module: required runtime ordering, state ownership, side effects.
▲ 0↑ born
▣
module · born here
❒asset
# flui The look of *First Light: Vagant*'s screens, in one module. `flscreens` builds the trees; this builds the pieces they are made of. ```luau local flui = require("ui.flui") ``` ## Measure — nothing here is a pixel Every size is a fraction of the **live short screen axis**, so the same tree composes on a phone held upright, on a laptop, and on a television. | Call | What it answers | |---|---| | `flui.size()` | the shape being laid out against | | `flui.u(f)` | `f` of the short axis, in logical points | | `flui.portrait()` | is the screen taller than it is wide | | `flui.cardWidth()` | how wide a reading column should be | | `flui.typeSize(scale)` | a type size, never below its floor | `flui.T` is the type scale as fractions; `flui.TMIN` is the floor in logical points beside it, because a reader's eye does not shrink with the screen — on a 390-point phone the fractions alone would land body text at 9 points. `flui.TAP` (48) is the smallest a thing a finger presses may be. `flui.previewSize` lays a build out against a **stated** shape instead of the live one. Set it, build a tree, read the numbers back, clear it: that is how screens authored in a tall frame are proved to hold in a wide one. ```luau flui.previewSize = { width = 1920, height = 1080 } local tree = require("ui.flscreens").title(model) flui.previewSize = nil ``` ## Colour `flui.C` carries `fl.PAL`'s four UI hexes plus this slice's own, derived from the same palette the fen and the lantern are painted from: `ink` `paper` `brass` `blood`, the panel steps `soot` `slate` `iron` `fog`, the light `flame` `lamp` `dawn`, `jasey` white, `water`, and the parchment set `vellum` / `vellumEdge` / `vellumInk`. ## Pieces - **Type** — `flui.text(str, scale, style)`, `flui.para(str, scale, style)` (a paragraph always ships inside a full-width box of its own; a wrapping label that carries its own width collapses to one glyph per line), and `flui.eyebrow(str, color)` for the letterspaced cap line over a heading. - **Ornament** — `flui.rule(opts)` (a hairline with a diamond set in it), `flui.diamond(size, color)`, `flui.svg(str, w, h, style)`. - **Surfaces** — `flui.plate(children, style)` for a panel on the dark page, `flui.vellum(children, style)` for a printed card, `flui.scrim(children, style)` for the ground a card sits on. - **Controls** — `flui.item(opts)` is the one menu line: a plate at thumb size carrying its own handler, with a brass ring, a lit edge marker and a diamond when `focused`. `flui.cap(str, kind)` draws a keycap, a pad button or a touch target as the thing it is. - **Glyphs** — `flui.lampGlyph(lit, height, onLight)`, `flui.wigGlyph(color, height)`, `flui.pauseGlyph(color, size)`, `flui.sunGlyph(color, size)`.
▲ 0↑ born
backing path · 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.