Stormlark
Thread the storm one column at a time and spend your charge on the cells you cannot dodge.
Goal
You fly the Stormlark, a small survey craft, down a storm canyon. The canyon is a grid 7 lanes tall and 10 columns wide, and your craft sits permanently in column 0 at lane state.shipLane. Everything else scrolls toward you one column at a time, and the world only moves when you do: each turn you take exactly one action, then the storm takes exactly one step. There is no clock and no time limit, so the whole game is a routing puzzle with nine columns of warning. Crags are basalt and cannot be cleared, so the only answer to a crag is to be somewhere else. Cells can be cleared, but a pulse costs 3 charge from a gauge of 9 that refills at 1 per turn, so firing every turn is arithmetically impossible. Every turn is a three way choice: reposition, clear, or bank. A dodge only path across the next 3 columns is proved to exist every single turn, so you are never trapped without warning, but nothing keeps the far end of the pipeline passable except you. Clearing is how you keep it open.
Scoring
Higher is better. Score accrues every turn, never decreases, and nothing ever subtracts. Two multipliers apply. The chain multiplier is 1 + 0.10 * min(chain, 20), capped at 3.0, where chain counts consecutive cells cleared; it increments on a clear, is preserved by a hit that damages a rimeshell or a warden without clearing it, and resets to 0 when you take hull damage or fire a pulse that clears nothing. The reach multiplier is min(4.0, 1 + 0.25 * (reach - 1)), so reach 1 pays 1.0 and reach 13 and above pay 4.0. The awards are: 2 for every column advanced, times reach; 10 for a mote collected, times reach; 50 for a knot cleared, 90 for a drifter, 140 for a sparker, 160 for a rimeshell and 500 for a warden, each times chain times reach; 40 times reach for reaching a Gate; and a further 60 times reach when that reach was completed without losing any hull. Clearing is where the points are: advancing columns and reaching Gates alone pays about a fifth of what a player who clears as well will score. Every award rounds to a whole number after multiplication. END_RUN pays nothing extra, on purpose, and that is true wherever it is played, so stopping is never worth more than carrying on. The plausibility guard is 1000000, which is far above any reachable run.
Rules
BOARD. 7 lanes, indexed 0 at the top to 6 at the bottom, by 10 columns, indexed 0 at the left to 9 at the right. Your craft is always in column 0 and never changes column. Every cell holds at most one entity. state.entities lists them all, each with id, kind, lane, col, hp and drift. ACTIONS. Exactly six, with no payloads: { "type": "UP" }, { "type": "DOWN" }, { "type": "FIRE" }, { "type": "VENT" }, { "type": "NEXT_REACH" } and { "type": "END_RUN" }. UP and DOWN shift you one lane. FIRE sends a pulse straight down your lane and meets the first thing in it. VENT holds your lane and banks extra charge. NEXT_REACH flies on into the next reach and is legal only at a Gate. END_RUN stops the run and keeps the score, and it is legal on EVERY turn of a run, at a Gate and in flight alike. You take exactly one action per turn; moving and firing in the same turn is never possible. ILLEGAL ACTIONS ARE REJECTED AND COST NO TURN. UP is illegal at lane 0, DOWN is illegal at lane 6, FIRE is illegal when charge is below 3, NEXT_REACH is illegal while phase is flying, and while phase is reachClear only NEXT_REACH and END_RUN are legal. END_RUN is the one action legal in both phases. A rejected action returns ok false with a reason and the real legal set; no turn is spent, no counter moves and the state comes back unchanged. getLegalActions always returns the true legal set, and it is never empty while phase is flying because VENT and END_RUN are always available. RESOLUTION ORDER, and it is normative. 1: the action is validated, and an illegal one returns unchanged. 2: the player phase, where FIRE spends 3 charge and scans columns 1 to 9 in your lane for the first entity that blocks a pulse, UP or DOWN moves you one lane, and VENT sets this turn's bank bonus. 3: every sparker currently in column 1 discharges along its lane and is then spent, so a sparker you cleared in the player phase never discharges. 4: drifters move one lane in their drift direction and wardens move one lane toward your lane on alternating turns, both clamping at lanes 0 and 6, reversing or holding when blocked, processed in ascending id order. 5: everything scrolls one column left and anything past column 0 is gone, which costs you nothing at all. 6: a mote or a squall now sharing your cell is collected or dissipates. 7: a solid now sharing your cell costs 1 hull and is sheared away whatever its remaining hp. 8: charge regenerates, after the collision, so a bad turn never also costs you your economy. 9: a fresh column is drawn at column 9. 10: the engine checks that a dodge only path across the next 3 columns still exists and repairs the board if it does not. 11: counters update. 12: at hull 0 the run is over and the score is frozen and kept. ENTITIES. Crag blocks a pulse and absorbs it with no effect, costs 1 hull on contact and cannot be cleared. Knot blocks, costs 1 hull, clears in 1 hit. Drifter is a knot that moves 1 lane per step in its drift direction and reverses at lanes 0 and 6. Rimeshell blocks, costs 1 hull, and takes 2 hits, cracking visibly after the first. Sparker blocks, costs 1 hull, clears in 1 hit, and discharges along its whole lane when it is in column 1. Warden blocks, costs 1 hull, takes 4 hits, moves 1 lane toward your lane on every second step, and at most 2 are ever alive. Squall blocks a pulse and absorbs it, does NO hull damage, drains 3 charge and then dissipates. Mote does not block a pulse at all, does no damage, and grants 3 charge when collected. Gate is a full width marker with nothing solid in it and ends a reach. CHARGE. You hold up to 9 and start every reach full. A pulse costs 3, whether or not it meets anything. Every turn pays 1 back and a VENT pays 3, so alternating VENT and FIRE is exactly sustainable and movement is the thing you trade away. A mote adds 3 and a squall removes 3. THE SURVIVABILITY GUARANTEE, AND ITS LIMIT. After every resolved turn the engine proves, by search from every lane at once, that SOME lane can cross the NEXT 3 COLUMNS using only UP, DOWN and VENT, and deletes blocking obstacles until one can. The board is therefore never sealed. The guarantee does not say the way through starts in YOUR lane: reaching the lane that threads it is your problem, and from some positions dodging alone will not get you there. Beyond column 3 nothing is guaranteed either: you can see all nine columns, and if the far end of the pipeline is closing you have to open it with FIRE or route around it in time. REACHES. A reach is 20 content columns followed by 1 Gate column, so 21 turns. When the Gate reaches column 0, phase becomes reachClear, the world freezes and the reach bonuses are paid. From there NEXT_REACH raises the reach by 1, tops your hull up by 1 to a maximum of 3, refills your charge to 9 and flies on, while END_RUN stops the run and keeps your score. END_RUN is NOT restricted to a Gate: it is equally legal on any turn while phase is flying, it resolves identically from either point, it pays no bonus either way, and it neither steps the world nor draws a column. Columns 1 to 9 already hold the next reach's content, so the step up in difficulty was visible nine turns before you committed to it. There is no reach cap, no wave count, no timer and no column limit: the run ends at hull 0 or at an END_RUN, and nothing else ends it. DIFFICULTY. Every reach adds more solids per column, a higher share of indestructible basalt, longer crag runs and fewer motes, and new entity kinds unlock one reach at a time: drifter at reach 2, squall at 3, rimeshell at 4, sparker at 5, warden at 6, full wall columns with a single gap at 8. Everything caps at reach 13, and reach 14 and above replay reach 13's world at reach 13's multiplier. No player facing number ever changes: hull is always 3 and charge is always 9 at every reach. DETERMINISM. A whole run replays from state.seed and your action list. The cursor lives in state.rngState and advances by a fixed budget on every turn that scrolls the world, by nothing at all on NEXT_REACH or on an END_RUN played anywhere, and by nothing at all on a rejected action. The feasibility repair draws nothing.
Action grammar
{
"type": "object",
"oneOf": [
{
"properties": {
"type": {
"const": "UP"
}
},
"required": [
"type"
],
"description": "Shift one lane up. Illegal at lane 0."
},
{
"properties": {
"type": {
"const": "DOWN"
}
},
"required": [
"type"
],
"description": "Shift one lane down. Illegal at lane 6."
},
{
"properties": {
"type": {
"const": "FIRE"
}
},
"required": [
"type"
],
"description": "Send a pulse down your lane. Costs 3 charge. Illegal below 3 charge."
},
{
"properties": {
"type": {
"const": "VENT"
}
},
"required": [
"type"
],
"description": "Hold your lane and bank 3 charge instead of the usual 1."
},
{
"properties": {
"type": {
"const": "NEXT_REACH"
}
},
"required": [
"type"
],
"description": "At a Gate only: fly on into the next reach."
},
{
"properties": {
"type": {
"const": "END_RUN"
}
},
"required": [
"type"
],
"description": "Legal on any turn: stop the run and keep the score. Pays no bonus, steps no column."
}
]
}Start a game
curl -s "https://gameboard.gg/api/games/stormlark/init?seed=7"
Then POST { state, action } to https://gameboard.gg/api/games/stormlark/action, carrying state forward each call. See the API overview for the full loop.