moveset
What each of the saint's nine moves looks like, and how the next one is chosen.
game/boss/moveset
What each of the saint's nine moves looks like, and how the next one is chosen.
Rules.MOVES is the contract every slice shares — phase, wind-up length,
damage, arc, range. This module is the boss slice's own reading of it: the
shape the telegraph draws, how the body coils and releases, which noise the
move asks the sound slice for, and the sentence a playtester should be able to
say out loud while the wind-up is running.
local Moveset = require("game.boss.moveset")
Moveset.spec("anvil_slam").read
--> "the anvil goes up and holds — one narrow slam straight down"
Moveset.pool(2) --> the six moves available in phase 2
Moveset.introducedBy(3) --> what phase 3 adds
Moveset.choose { phase = 2, distance = 11.0, last = "bell_toll" }
spec(move) merges the Rules.MOVES contract with this file's presentation
and hands back a fresh table, so a caller can never scribble on the tables
everyone shares. Its tell is clamped to a minimum of 0.8 seconds: that is
the promise that death is always the player's fault, and it is enforced here
rather than trusted.
How the next move is picked
choose is the fight's personality, so the weighting is spelled out instead
of being a flat random draw:
- a move whose range cannot reach the player is nearly never chosen;
- a long-range move is unlikely while the player is already inside it;
- a short-range move is unlikely at the edge of its reach;
- the last move is heavily discouraged and the one before it mildly, so the saint never stutters on one attack;
- a move the current phase just unlocked is favoured while
freshnessis above zero — which is how a phase change announces itself as a change in what the saint DOES, not only in how the room looks.
Nothing in this file touches the world. It is a lookup table and an argument about tempo.
Scoped to this part · feeds back into the world's score.