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

NearsideCam

The one camera of *First Light: Nearside*. A camera behaviour: it sits on the local player's `CameraRig` beside the `Camera` component, where `Staging` puts it, so every joining peer gets it and nothing else in the scene has to know.

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

NearsideCam

The one camera of First Light: Nearside. A camera behaviour: it sits on the local player's CameraRig beside the Camera component, where Staging puts it, so every joining peer gets it and nothing else in the scene has to know.

It is the chase and every staged moment. The opening, the beat when the cargo starts to cook or to freeze, the swing onto the airlock and the fall away from a rover the light caught are all this rig on a different directive — which is why none of them can cut.

The framing is solved, not authored

A shoulder rig is a distance and a pitch, and where it puts the subject on screen depends on the shape of the window. This one is told where on the frame the rover goes and solves the pitch for it against the live field of view:

  • the rover lands at roverScreenY down the frame — 0.76 on a tall screen, 0.72 on a wide one, blended by aspect between them;
  • at least minTopSky degrees of sky stay above the horizon, which is the band Earth hangs in;
  • the aim yaws a little toward the corridor's centre line, so the dome at the head of the route stays in shot when the rover runs wide.

A 3:4 phone held upright and a 21:9 monitor therefore get the same composition out of different numbers. The anchors live in game.staging.shots.

Directives

Plain tables, never functions, so a reload of the director cannot leave the camera holding something dead. Each one cross-fades from whatever pose was on screen over its own blend.

kindWhat it does
followThe chase. The default, and where every other directive returns to.
poseStand at eye, aim at at, on fov.
orbitCircle center at radius and height, turning at yawRate, aiming between the rover and the centre by lookMix. The winning shot.
pullbackRecede from the rover along its own heading, rising and drifting to the side at rate. The losing shot.

Two modifiers ride on top of any of them: pushIn(strength, hold) bites the frame in and narrows the lens, and shake(amp, seconds) knocks the eye about.

Reading it

local cam = NS.get("stagingCam")
cam.aim()      -- eye, aim point, lens, push-in, and the framing they came from
cam.mode()     -- "follow" | "pose" | "orbit" | "pullback"

Fields

follow · groundLag heightLag yawLag aimLag fovLag (seconds; the rig covers about 63% of the distance to its solved pose in one) · distanceTrim heightTrim screenYTrim (nudges on top of the solved framing) · pushRise pushFall.

Interface

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

conforms to

zero/source-extract/v2

NearsideCam — the one camera of FIRST LIGHT: NEARSIDE. A camera behaviour. It lives on the local player's `CameraRig` beside the `Camera` component, put there by `game.staging.Staging` at awake in place of the built-in `third_person_follow`, so every joining peer gets it and nothing else in the scene has to know. It replaces the built-in rig for two reasons. The built-in one is an authored shoulder offset — a fixed distance and a fixed pitch — which frames a subject wherever the aspect ratio happens to put it; this one is told WHERE ON THE FRAME the rover must sit and solves the pitch for it against the live field of view, so a 3:4 phone and a 21:9 monitor get the same composition. And the built-in one only follows: the opening, the beat when the cargo starts to cook or freeze, and both endings all need the camera to leave the chase, so the chase and those moments have to be the same rig or the hand-over jumps. It is driven by DIRECTIVES — plain tables, never closures, so a hot reload of the director does not leave the camera holding a dead function: local cam = NS.get("stagingCam") cam.set({ kind = "orbit", center = ..., radius = 20, ... }) cam.follow(1.2) -- back to the chase over 1.2 seconds cam.pushIn(0.8, 0.5) -- the beat: in hard, hold, ease out cam.shake(0.35, 0.8) Every directive cross-fades from whatever pose was on screen, so nothing it is asked to do can cut.

v(x: number, y: number, z: number) → Vec

argtypedescription
xnumber
ynumber
znumber

damp(a: number, b: number, tau: number, dt: number) → number

argtypedescription
anumber
bnumber
taunumber
dtnumber

dampVec(a: Vec, b: Vec, tau: number, dt: number, tauY: number?) → Vec

argtypedescription
aVec
bVec
taunumber
dtnumber
tauYnumber?

lerpVec(a: Vec, b: Vec, t: number) → Vec

argtypedescription
aVec
bVec
tnumber

shortestAngle(from: number, to: number) → number

argtypedescription
fromnumber
tonumber

subjectId( ) → string

What the camera is looking at.

subjectPose( ) → void

framing( ) → Shots

finishZ( ) → number

solve(dt: number) → void

Solving one frame's pose.

argtypedescription
dtnumber

writePose(eye: Vec, at: Vec, fov: number) → void

Writing the pose onto the rig.

argtypedescription
eyeVec
atVec
fovnumber

repose(dtHint: number?) → void

argtypedescription
dtHintnumber?

beginDirective(d: any) → void

The surface the director drives this through.

argtypedescription
dany

set(d: any) → void

Put the camera on a directive: `follow`, `pose`, `orbit` or `pullback`. It cross-fades from whatever is on screen over `blend` seconds.

argtypedescription
dany

follow(blend: number?) → void

Return the camera to the chase, over `blend` seconds.

argtypedescription
blendnumber?

snap( ) → void

Seat the chase immediately at the rover, with no blend — used when a cutscene hands the viewport back.

pushIn(strength: number?, hold: number?) → void

The beat: push in by `strength` (0..1), hold it for `hold` seconds, then let it out.

argtypedescription
strengthnumber?
holdnumber?

shake(amp: number?, seconds: number?) → void

Shake the eye by `amp` metres, decaying over `seconds`.

argtypedescription
ampnumber?
secondsnumber?

mode( ) → string

Which shot the camera is on: "follow", "pose", "orbit" or "pullback".

aim( ) → any

The pose on screen this frame — eye, aim point, lens and push-in — for verifying a shot without unprojecting anything.

awake( ) → void

Lifecycle.

start( ) → void

onEnable( ) → void

onPropertyChanged(_key: ?, _value: ?, _oldValue: ?) → void

argtypedescription
_key?
_value?
_oldValue?

onFollowChanged(newFollow: any, oldFollow: any) → void

Re-seat the shot after the Camera's `follow` slot changed. The Camera probes for this by name, and a behaviour that does not declare it raises on every player spawn.

argtypedescription
newFollowanyThe entity the camera now follows.
oldFollowanyThe entity it followed before.

examples

-- called by Camera.onPropertyChanged, not usually by hand

update(dt: number) → void

argtypedescription
dtnumber

Sub-parts

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

11items
▣
module · born here
❒asset
# game.staging.shots The framing maths and the shot lists of *First Light: Nearside*. Pure data and pure functions: `NearsideCam` reads it to stand the camera up, `Staging` reads it to build the opening and the two endings, and either can be evaluated on its own against a made-up screen size. ```luau local Shots = require("game.staging.shots") local f = Shots.framing(1080, 1440) -- the portrait framing local eye, at, fov = Shots.solveFollow(rover, heading, f) Shots.screenYOf(eye, at, fov, point) -- 0 at the top edge, 1 at the bottom ``` ## Nothing here is a pixel A framing is stated as *the rover sits this fraction down the frame, with at least this many degrees of sky above the horizon*, and the camera pitch that produces it is solved from the live field of view. Two anchors — `TALL` for the 3:4 shape the game films at, `WIDE` for a landscape monitor — blend by aspect, so a window between them slides rather than snaps and a resize never jumps. `screenYOf` is the inverse: hand it a world point and it says where that point lands down the frame. It is how a shot is checked without eyeballing a capture. ## What is in it - `framing(w, h)` — the framing for a screen, blended by aspect. - `solveFollow(rover, heading, framing, pushIn?)` — the chase: eye, aim, lens. - `screenYOf(eye, at, fov, point)` — where a point lands down the frame. - `intro(ctx, short?)` — the opening, as a cutscene definition, built around wherever the rover actually stands. `short` is the retry's re-establish. - `stationCenter(rover, finishZ)` — the station the winning shot points at. - `wonDirective(rover, finishZ, framing)` — the swing across Peary Station. - `lostDirective(framing)` — the fall away from a rover the light caught.
▲ 0↑ born
▣
module · born here
❒asset
# ns The shared contract of *First Light: Nearside*. Every slice of the game requires this module and nothing else of each other's. ```luau local NS = require("game.ns") ``` ## What it carries - **Constants** — `NS.ROUTE` (the corridor: `startZ`, `finishZ`, `halfWidth`), `NS.DAWN` (the terminator's start, speed and the hot/cold distances), `NS.CARGO`, `NS.FUEL`, `NS.ROVER`, `NS.PALETTE`, `NS.AUDIO`, `NS.NAMES` (the entity names the scene authors) and `NS.COMPONENTS` (the component identities the entrypoint mounts). - **The light** — `NS.lightAt(dz)` and `NS.bandAt(dz)` read the sunlight at a distance from the dawn line, `dz = z - terminatorZ`. The rules and the lighting both read these, so the meter and the picture agree. - **The screen** — `NS.screen()` and `NS.unit(fraction)`: every layout is stated against the live screen size, never in pixels chosen for one frame. - **Events** — `NS.on(name, fn)` / `NS.emit(name, payload)`; the names are in `NS.EVENTS`. - **Phases** — `NS.phase()`, `NS.setPhase(p)`, `NS.resume()`, `NS.tick(dt)`, `NS.runTime()`. Phases: `title → howto → intro → playing ⇄ paused → won | lost`. - **Surfaces** — `NS.provide(name, table)` / `NS.get(name)` / `NS.whenProvided(name, fn)`. The shapes are in `/zero/source/design/spec.md`. - **Maths** — `clamp`, `lerp`, `smooth`, `approach`, `hash`, `rgb`. ## Notes - Module-local state (phase, surfaces, handlers) resets on hot reload of this file; components re-provide their surfaces in `awake`. - The route runs north, +Z. South of the terminator is sunlight.
▲ 0↑ born
backing path · game/staging/NearsideCam.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. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬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.