Game
The rules of *First Light: Lucina*. Mounted on `mount_rules` by the scene entrypoint; there is exactly one in the world.
Game — the night's law
The rules of First Light: Lucina. Mounted on mount_rules by the scene
entrypoint; there is exactly one in the world.
It owns, and is the only thing that writes:
Core.state field | what it means |
|---|---|
phase | title opening play paused fixing result won lost |
level / levelData | which plate is being exposed, and its definition |
dawn | 0 deep night … 1 first light. The night's clock. |
dose | 0 … 1. Fills while crystals stand on the plate. At 1 the night ends. |
cells[i].exposure / .masked / .target | the glass itself |
match | share of cells reading right, recomputed every tick |
results | one { level, match, passed } per plate fixed |
tray | stocked from the plate's case at levelStart; the board spends it |
The night
title ──start──▶ opening ──skip/next/7s──▶ play ──fix / dawn crests──▶ fixing
│ │
dose fills │ ▼
▼ result
lost next ──┤
pass → next plate │
fail → same plate │
fifth plate passes ──▶ won ◀──────┘
paused sits over play and holds every clock in it — the dawn, the dose and
the exposure all stop and resume exactly where they were.
What it listens to
uiRequest(action) on the Core bus, and nothing else. The actions:
start, skip, next, fix, pause, resume, restart, title. Every
one is answered against the CURRENT phase and ignored elsewhere, so a request
arriving twice does nothing twice.
What it fires
phase(new, old), levelStart(level, plate), doseWarning(),
dawnCrest(), plateFixed({ level, match, passed }), and sfx(name) for
dose_warning, dawn_crest, plate_fixed, plate_pass, plate_fail,
night_ends, relight.
Fields
openingSeconds(7) — how long the staged opening holds if no beat ends it.fixSeconds(3.2) — the length of the first-light sweep.autoAdvanceSeconds(0) — leave at 0 for a played game; above 0, every card answers itself after that many seconds, for an unattended run.
Methods
local game = entity.find("mount_rules").component.get("Game")
game:request("fix") -- the same thing Core.request(action) does
game:step(12) -- advance the night by 12 seconds, no engine clock
game:startLevel(3) -- jump to a plate
game:snapshot() -- phase, level, plate, dawn, dose, match, …
game:audit() -- right/wrong/total plus the missed and spoiled cells
game:render() -- the glass as ten strings: # dark, : spoiled, - masked
step runs the same body update does, so a test drives the real machine
rather than a copy of it.
The plates
game.rules.plates holds the five, and each carries the solution it was
authored from. Plates.verify() re-derives every claim — that the solution
scores a full plate, that the tray holds it, that the dose and the exposure
time fit inside the night, and that the same crystals WITHOUT the friskets
fail. Run it after retuning anything in Core.EXPOSURE or Core.DOSE.
Scoped to this part · feeds back into the world's score.