The pattern data
Tuning never changes code. The sim reads a pattern set of ten JSON files, validated at load; a missing or mistyped key fails with patterns/<file>: <key>, and a rung whose pools cover no path for a class is a load error, never a silent upgrade.
| File | Holds |
|---|---|
paths.json |
Named entry paths as unit control points, tagged by sprite class and tier. Honest and lie formations of one class draw from the same pool by construction. |
formations.json |
Member layouts for a burst of two to four calls, and the base box per sprite class |
fire.json |
Per tier: formation fire (null at tier 0), boss fire (with aim), and the dive rhythm (null at tier 0) |
bosses.json |
Per boss kind: size, health, rage factor, and the phase list (duration, motion, fire, cue) |
ladder.json |
The derive rule from a tape header to a tier, and four rungs (0–2 derived, hardcore selector-only): path pools, who fires, speed, fog, lamps, grace, hazards, rage from the start |
waves.json |
Per tier: breather, beats per group, rest, density, and the tail after the last wave |
player.json |
The ship’s speed, cooldown, hitbox, row and grace |
drops.json |
What a downed boss and a cleared formation drop, how fast it falls and drifts, the catch box, how long each fire drop lasts, and the draw weight among the three fire drops (spread, rapid with its own cap and cooldown, pierce) |
voice.json |
Four dry lines per wave kind, boss kind, and the end scene, picked by the round seed |
parallelism.json |
Per tier: whether a burst is on, how many honest extra copies on the first wave and on the last (copies, copiesLater), how long the first burst vs later ones, the quiet gap, fire intensity on the first wave and on the last (intensity, intensityLater), whether the extras shoot. The copies and the intensity climb by wave |
shift.json |
Shift length (four), climb [0, 0.35, 0.7, 1], the two word lists for the code, and flavors[]: role, biome, exclusive verbs, optional midboss, telegraph words. Trough is encounter shape on the third call, not a dip in climb. |
ladder.json derives the tier from the tape header, first matching rule wins:
- a live server over stdio → 2
- a docker target with an image id → 2, even when seated
- a seat on a non-live target → 1
- a fixture, or docker with neither image nor seat → 0
The shell can override the derived tier (the difficulty selector), and the fairness band does the same by editing only the header.
What each lever does
Section titled “What each lever does”fire.json→ tiers.N.formationperiod,burst,spread,speed: how often a formation fires straight down, how many shots, how wide, how fast. Null at tier 0 by schema. Four more, each optional:shootersnames the spawn classes that fire (grid and menu when absent);onEntrylets a sprite fire on its way in once it is on the field, not only from the hover (off when absent);aimpoints the shot at the ship rather than straight down (off when absent; on at every rung that fires since 2026-09-17, so an edge is not a quiet corner);fromAboveis how far above the ship’s row a sprite must be before it fires at all (zero when absent; a quarter of the field at every rung that fires, so a sprite returning from the bottom or swooping low never fires from beside the ship, where a shot cannot be dodged);firstShotis when a sprite’s first shot comes, as a fraction of the period (one when absent, which is a whole period of silence before the first shot). Measured on 2026-09-17 with the shipped values: a player holding fire killed eighteen of twenty-seven sprites on their entry paths and the formation fired zero to six shots in a seat round, so only the boss ever attacked. Set on 2026-09-17 by the Director’s play: every class fires at every rung above the recorded one, on the way in, with an early first shot; hardcore fires every 1.6 seconds with wider spread and a faster boss and dive. The fairness band’s bars for those rungs were re-based to what its bots measure at that heat, and say so beside the bar.ladder.json→ rungs[].sweep, sweepPeriod how far a hovering formation swings across the field as one block, as a fraction of the room on either side of it (zero is a six-pixel wobble about its home), and how long one full swing takes. Set on 2026-09-17 so the outer sprites reach the edges and the spacing holds.ladder.json→ rungs[].shotsInFlight how many of the ship’s shots may be in the air at once; absent or null for no cap. Eight shots a second at a speed that crosses the field in under a second is a wall of six; Galaga’s rule is two. A cap makes the aim matter and gives a formation time to enter.fire.json→ tiers.N.boss the same plusaim: withaimthe burst fans around the line from the boss to the ship as it is when the shot leaves.fire.json→ tiers.N.boss.pilotfan,spread,lean,cadence,lookAhead: what an Ollama seat may do beyond the phase script. A seat’sspreadis a fan offanshots acrossspreadof the field, straight down; a pendingcolumnslides the boss up toleanpixels toward the ship before an aimed shot;holdis a silent beat that keeps the boss still.cadenceis beats between next-verb asks;lookAheadis how many closed verbs one ask may queue. Thin on the recorded rung. The seat never sees a fact, so none of this can either.fire.json→ tiers.N.diveperiod,speed,depth: how often a hovering formation dives, how fast, how far down as a fraction of the field. A dive tracks the ship until half its depth, then commits.bosses.json→ hp, rage: health in hits; the fire-period multiplier below half health (in 0..1). Phases whose motion isslitorholdtake no damage.waves.json→ density, tail: seconds per beat is a base over density, so a higher density packs the same beats into a shorter round; the tail is how long the round runs after the last wave closes. Placement is stretched so the last wave ends a tail before time-up.ladder.json→ rungs[].speed, fog, lamps: a multiplier on entry and dive speed, a multiplier on fog drift, and the number of lamps.drops.json: a downed boss always drops a lamp; a cleared grid formation always drops a spread. Fall speed, drift (0 = straight down), catch box, spread duration. Never keyed on a lie.voice.json: lines named by atom kind and boss kind, never by fact. The loader rejects a digit or a fact word. With the Ollama seat on, each boss picks which of its own lines it says at spawn; the seat can only choose from this file, never write to it.parallelism.json: a seed-placed burst per wave. Off on the recorded rung. First burst is short; later waves hold it longer. Extra copies are always honest, even when they clone a lie’s class. The soundtrack lays the parallelism bed over the wave’s bed for the burst and ducks the wave’s, then lifts it off; it never swaps songs. Copies and intensity climb wave by wave from their first-wave values to their last-wave values.
Measuring a change
Section titled “Measuring a change”pnpm sweepplays every tape at every tier with every bot and prints, per tier and bot, the dead rounds, the mean lamps lost and the lies revealed. Change one lever, re-run, compare. Then
pnpm testis the andon: the fairness band fails the build if the change made the game a gallery or a wall.
What a change may never do
Section titled “What a change may never do”Nothing in the pattern set may key on whether a sprite is a lie. Paths are chosen by class and tier; fire and dives by class and tier; boss phases by kind. The loader rejects any boss phase with a key named lie, fact, revealed or followed, and the sim tests flip a tape’s fact and require identical boss trajectories, dive schedules and shot velocities.