Helm
The wheel, the chase camera and the screen of *Ninja Pirates of the Baltic*. SLICE C's whole implementation lives here; `game.helm` only stands it up on one transient entity.
Helm
The wheel, the chase camera and the screen of Ninja Pirates of the Baltic.
SLICE C's whole implementation lives here; game.helm only stands it up on one
transient entity.
Attach it to any entity — it drives NPB.state.boat and the local player's
CameraRig, not the entity it sits on.
entity.spawn("HelmRig").component.add("game.helm.Helm", {})
What it owns
| The ship | throttle and rudder with a hull's mass, heel into the turn, and the pitch and roll the real ocean puts under the keel |
| The camera | a trailing rig with the horizon damped separately from the boat's bobbing, so a wave lifts the junk without lifting the sea |
| The guns | a broadside, its muzzle flash and its shot, and the "fire" event the rules slice resolves |
| The screen | npb_hud (hull, stones, speed, bearings, battery), npb_damage (the hit vignette) and npb_overlay (title, win, loss) |
Fields
| Field | Default | What it is |
|---|---|---|
inputMap | game.helm.helm | The controls the wheel answers to. Point it at another .inputMap to rebind without touching this file. |
How it feels, and where the feel lives
Every number is in one of three tables at the top of init.luau — SHIP,
GUNS, CAM — and each exists to put mass between a key and the hull:
tauAccel/tauDecel— the seconds the engine takes to work up to a demanded speed, and the longer time way takes to fall off it.tauRudder— the blade swinging toward the ordered angle. The key orders the rudder; it does not turn the ship.turnAuthority×ratedSpeed— the hull answers the blade only in proportion to the way it is making, so a junk stopped in the water does not turn however hard the wheel is over. About a 40 m circle at 15 m/s.CAM.smoothXZvsCAM.smoothY— the trail is snappy and the height is heavily damped, which is what keeps the horizon still while the deck rides.
What it publishes
NPB.claimHelm() in awake — the frame the component exists, which stands
boot's placeholder drift and placeholder camera down before either can fight
it — and NPB.registerHelm { throttle, speed, heading, aim }.
aim() is the one with a decision in it: with a raider inside GUNS.range the
broadside is laid at it, and with the sea empty it goes out over whichever beam
the volley would have gone over anyway. That is what lets the rules slice fly a
volley without knowing anything about the wheel.
"fire" carries { from = { x, y, z }, dir = { x, z }, boat, range, side } —
the muzzle as plain numbers, the entity for a listener that would rather have
the hull, and the reach the guns were laid for.
What it listens to
hit (the vignette and the hull line), raiderSunk, stoneCollected,
riftOpen, win, lose, reset. Every number on the screen is read out of
NPB.state at draw time, so the HUD is right whatever the rules slice does with
them.
Modes
update is play-only, so the HUD is a play-mode surface by construction.
editorUpdate takes the screens down and forgets that play began, which is what
keeps a HUD off another author's viewport and puts the title card back up at the
start of the next run.
Scoped to this part · feeds back into the world's score.