raid
Everything that decides what happens in *Ninja Pirates of the Baltic*.
raid — SLICE A: rules & loop
Everything that decides what happens in Ninja Pirates of the Baltic.
game.boot starts it with Raid.start(NPB) and stops it with Raid.stop().
Nothing else requires it — the other slices reach it only through the events
and state declared in game.npb.
The loop it runs
NPB.MATCH.raidersronin raiders are on the water, spawned 190–520 m off the player and topped back uprespawnDelayafter each sinking.- They close, circle at broadside range, loose a volley whenever the player is abeam, and sheer off before coming round again. Two press the attack at a time; the rest stand off and wait their turn.
"fire"from the helm throws the player's broadside. If a raider sits inside the aim cone the volley is laid on a lead solution for it — a broadside from a rolling deck is pointed at a ship, not at a pixel.- A raider out of hull goes down and leaves a rift stone glowing on the water.
Sailing within
pickupRadiusof one takes it. - The
stonesToWinth stone lights the rift. Crossing intoNPB.RIFTwins. Hull to zero loses. - Either ending holds for
resetDelay, then the run starts again from exactly the state the first one started from.
Raiders press harder the more stones the player is carrying.
What it publishes
It raises every event in the contract — hit, raiderSunk, stoneDropped,
stoneCollected, riftOpen, win, lose, reset — and it is the only
writer of NPB.state.phase, hull, stones and sunk. The HUD is built on
those and needs nothing else from here.
It gets boats from NPB.fleet() and never builds one. It reads the player's
heading and aim from NPB.helm() and never reads input.
Raid.report() returns the whole match state as plain numbers, for probing
from an execute call.
Layout
init.luau | the numbers, the match flow, the rift stones, the one driver loop |
game.raid.raiders | the fleet and its behaviour |
game.raid.gunnery | shot flight and hit resolution, both directions |
Rules it follows
One task.spawn drives the whole slice, so there is exactly one handle to
cancel, and it is guarded with NPB.isCurrent(gen) so a reload retires it.
Every floating thing keeps its own logical x / z and never reads its
position back off the entity. Every entity is NPB.transient. All timers are
wall clock rather than accumulated frame time — a frame that overruns has its
dt clamped, so an accumulator drifts behind the clock under load and a
seven-second delay quietly becomes eleven.
Scoped to this part · feeds back into the world's score.