GameLoop
The rules of *First Light: Vagant*, on the `mount_loop` entity. It is the only writer of `fl.state`; every other slice reads that table or subscribes through `fl.on`.
GameLoop
The rules of First Light: Vagant, on the mount_loop entity. It is the
only writer of fl.state; every other slice reads that table or subscribes
through fl.on.
It owns the dawn clock, the lantern and its oil, the relight, the jasey going
on and off and getting soaked and dried, the vagant's walking and wading
speed, and the win/lose machine. It owns vagant.inputMap — the world's one
control scheme, carrying keyboard, gamepad and touch on every control — and
therefore the on-screen touch buttons the night is played with.
Public fields
inputMap: assetRef(inputMap)— the scheme to run. Defaults tovagant.autoStart: bool— start the night without waiting for a title card to be taken. Off by default: the loop sits intitleuntil something callsfl.beginRound(), which is what thescreensslice's title card does.
The one scheme
The engine's CharacterController (on the vagant's body) and
third_person_follow (on the CameraRig) each activate an input map of their
own, defaulting to @builtin::inputMaps.default. Left alone that map is live
beside this game's: two movement sticks, five touch buttons that do nothing,
and hood fighting the default look for the right mouse button.
So at the frame the local player is ready, this component walks the avatar
and camera-rig subtrees and points every component carrying an inputMap
field at vagant.inputMap. That field is read once, when a controller arms,
and none of them watch it afterwards — so the gameplay clock is held for ONE
frame and let go, which is the hook they already register
(CameraRig.driveWhileRunning) to hand their controls back and take them
again, re-reading the field on the way in. It has to be a real frame: holding
and releasing inside one call re-arms them on the map they already had.
Two things then still draw the engine's own buttons on a phone, and both are
cleared once every controller is re-armed: whatever still holds
@builtin::inputMaps.default at the scheme layer is released, and the
compatibility action registry the touch overlay also draws from is displaced
with an empty one. After that the overlay carries exactly this game's five
buttons — Relight, Hood, Run, Climb, Pause — plus the movement stick and the
look-drag zone.
Driving the night from another slice
screens and staging call these rather than writing fl.state:
fl.beginRound()— a title card was taken; run the staged opening.fl.openingDone()— the opening beat has finished; hand the fen over. If nothing calls it, the night starts anyway afterfl.TUNE.OPENING_MAX_SECONDS, so a missing cutscene can never strand a player on a dead screen.fl.setMenu(open)— a card went up or came down. Holds the clock and the vagant while it is up.fl.restart()— an end card was taken; walk out onto the fen again.
Phases
title → opening → play → won | lost, with paused sitting beside play.
Every change fires phase(new, old).
Lamps
The fen slice dresses the seven anchors and hands each one over with
fl.registerLamp. If nothing has by fl.TUNE.LAMP_REGISTER_GRACE seconds
into the night, this registers the bare scene anchors instead and gives each
a stand-in warm light when it is relit — a night is always winnable. A fen
that shipped registers first and none of that runs.
Water
fl.waterDepthAt(x, z) answers how deep the vagant is standing. The fen
slice hands the probe over with fl.provideWater(fn); until it does the
answer is dry ground, which is a walkable fen rather than a broken one.
Past TUNE.WET_DEPTH the wig soaks; past TUNE.WADE_DEPTH the walk drops to
TUNE.WADE_SPEED and running is refused; past TUNE.DOUSE_DEPTH the lantern
goes out.
Lifecycle
awake()— resolves the scheme, registers the loop driver onfl, subscribes tocaughtandhunt, and enterstitle.update(dt)— the night, one frame at a time. Play mode only.editorUpdate(dt)— hands the scheme back whenever the gameplay clock is held, so the pointer belongs to the editor's camera while authoring.onDestroy()— releases the scheme and every subscription.
Usage
entity("mount_loop").component.get("GameLoop") -- the mount the scene carries
fl.beginRound() -- start a night
Scoped to this part · feeds back into the world's score.