Usage (CLI)

space-rider                          Start in normal mode
space-rider --debug                  Open the debug scenario menu
space-rider --mode <mode>            Start a specific debug mode directly
space-rider --help                   Show help

Controls

InputAction
W / A / S / D or Arrow keysSteer ship
F or Shift (browser)Boost
SpaceFire pulse cannon
Q / EBarrel roll
PPause / resume a run
MMute toggle
C (browser)CRT scanline overlay on / off
V (browser, title screen)Open a two-pilot split-screen round
EnterLaunch / relaunch
EscReturn to menu / quit
Ctrl+CQuit immediately (terminal)
Arrows + Enter (name entry)Spell and file a three-character name
Thumbstick / FIRE / BOOST (touch)Steer / fire or confirm / boost
Left stick / A / B / triggers (gamepad)Steer / fire or confirm / boost / barrel roll

A gamepad is the browser version's third way in, beside the keyboard and the touch controls, and it feeds the same two inputs both of those do — so nothing about a run knows which one is flying it. It has to report the standard layout before it drives anything: the controls are read by their position in that layout, and a wheel or a flight stick read the same way would steer on whatever axis happened to come first. The left stick takes the dead zone and the eight even sectors the on-screen thumbstick already uses, so one tuning governs a thumb on glass and a thumb on a controller. A is the trigger during a run and the confirm key on the title screen, the debug menu and the game over screen, which is the same reading the on-screen FIRE button takes. Any pad plugged in can fly the run and a second one alongside does no harm, because the slots are read together rather than chosen between. In a split-screen round the whole pad flies pilot 1: the stick steers on that pilot's own keys and A is that pilot's trigger, because one controller is one pair of hands and there is no reading under which half of it belongs to the other seat. That holds for as long as the round is being flown: once both pilots are down, the result panel is a screen waiting on Enter like any other, and A is its confirm key again rather than a trigger with nobody left to fire it. The on-screen FIRE button lands on the same pilot, and comes back on the panel with it, for the same reason. The thumbstick beside it stays out of a round, since a round wants two seats and a touchscreen has no keys for the second one.

M silences the browser version and shows a MUTED indicator: in the footer during a run, and on the bottom border of the menu screens outside one — the title screen, the name entry screen, and the game over screen. Mute is the page's setting rather than a run's — it survives a death, and a split-screen round opened afterwards carries it in — so the screen you press it on says which state you are in. The game over screen matters most of the three, because it is where you arrive straight out of a run and so the likeliest screen to press the key on. The debug scenario menu is the one screen left without the indicator: it is reached only by --debug or ?debug, so it is a developer's screen rather than a session anybody is listening to. The terminal version has no audio, so there the flag only raises the indicator.

The name entry screen reads the same keys as a run, spelling rather than steering: up and down walk the character under the cursor through A-Z, the digits and a space, left and right move between the three slots, and Enter files the score under the name showing. Esc files it as well rather than discarding it - the score was earned before the screen came up, so backing out is a choice about the name - and then leaves for the title screen, where the table is.

The keys that act on the press rather than the hold — P, M, Esc, and the Q and E roll — fire once per press. Holding one down does not repeat it at any of the repeat rates the standard OS sliders offer.

That costs a short deadzone after a hold, because a terminal has no key-up event and silence is the only thing that can stand in for one. P, M and Esc take it flat: having held one, the next press of it is ignored for a beat, so a deliberate second tap needs a moment between them. Nothing is competing for those keys, and a beat is cheap.

The roll is the escape move, so Q and E do not pay that flat rate. Their deadzone is measured off the repeat stream instead, which lasts about twice the gap between repeat characters — a fifth of a second on a normally configured keyboard. Let go after a long hold and the next press rolls, with the roll cooldown left to decide whether it lands. What cannot be recovered is a re-press inside the repeat rate itself: arriving exactly as soon as the next repeat character would have, it is the same stream, and nothing tells them apart.

Silence only counts against a key while the game is listening for it. A terminal that cannot keep up with the repaint blocks the write the game hands it, and the loop with it, so keystrokes queue up and land together when it clears. That pause is the game's, not the player's, and it is discounted rather than read as the key having been let go — so holding the roll on a terminal falling behind still rolls once. A press made inside such a pause is the one thing lost: it cannot be told from the hold carrying on, and the game was frozen for it anyway.

Gaps that pause leaves are not the repeat rate either, so they are not measured as it. On a terminal that blocks on every frame there is no gap left that was timed against a loop actually reading, nothing is measured, and the roll falls back to the same flat deadzone P, M and Esc carry. That is the wide end rather than the narrow one, and on a loop ticking that slowly it is about three frames.

Gameplay

Split Screen

The browser version seats two pilots at one keyboard. V on the title screen opens a round, and index.html?versus goes straight into one. The canvas divides into two halves with a divider between them, and each half is drawn by the same renderer a whole screen is - the same HUD, the same footer, the same tunnel - with the pilot's name over the frame in place of the shared title and that pilot's own keys along the foot of it in place of the shared legend.

ControlPilot 1Pilot 2
SteerW A S DI J K L
FireSpaceEnter
BoostF;
Barrel rollQ / EU / O

Pilot 1's seat is the solo game's own, so a player who has flown alone already knows half of it, and pilot 2's is the same shape moved to the right hand. No key appears in both, which is what lets two hands hold a turn at once. The arrow keys and Shift steer nobody in a round: they are pilot 1's alternates in a solo run, and a control two players cannot agree the owner of is worse than no control.

P pauses the round and M mutes it from either hand, because a paused pilot beside a flying one is a round nobody would accept the result of. C still toggles the CRT overlay, which is the page's rather than the round's. Esc leaves for the title screen the round was opened from. Mute is a setting rather than a property of a run, so a round opens at whatever the page was left muted to, and a rematch opens at whatever the round was left on.

Both pilots launch into the same tunnel. One seed is drawn when the round opens and both halves lay their opening field from it, so the two of them start in the same rocks at the same depths. The fields drift apart from there, because each pilot reaches a given depth at their own speed and a pilot who shoots a mine spends a draw on the drop it may leave. The opening is shared; the flight is each pilot's own, which is what makes the two scores worth comparing.

Everything else is the solo game, twice over: each half keeps its own score, shield, combo, powerups, warp steps, asteroid sections and boss fights, under the rules the whole screen already has. The round ends when neither pilot is still flying, and the higher score takes it - score is what the game measures a run by and most of it is distance, so surviving is what earns it. A dead heat says so. Enter starts a rematch on a fresh seed, which is why the seed is never one a player could learn. A rematch opens a round, so it is held to the same floor the key on the title screen is held to: on a window dragged under it the panel asks for the window back rather than offering a key it would refuse.

A round never reaches the leaderboard. The table has one name to a row and a round has two pilots, so the round's scores are on the round's own panel instead. A pilot can still set the stored best, because a half is a real run flown under the ordinary rules by one player.

The damage shake is the one thing a round does without. The canvas is one object and the offset carries the whole of it, so a crash would shake the tunnel the other pilot is threading - a handicap earned by their rival's mistake. The damage flash each half already draws is what reports a hit there.

A round needs 45 columns a half, so 91 columns of grid, and the title screen offers the key only on a window wide enough to open one. That floor is the width the HUD's three readings want rather than the width the tunnel wants: npm run probe -- versus-half is what measures it. A window dragged narrower mid-round keeps the round, because taking one away from two players in the middle of it is worse than two narrow halves - and a half under the floor shortens its own readings rather than writing them over each other, so the score is still there to read at the 60 column grid floor. The distance loses its unit first, then the whole reading, then the shield's legend, the way each pilot's key list already drops to shorter forms. What the floor gates is opening a round, which is the title screen's key, a ?versus link and the rematch alike.

Debug Modes

Access via --debug (CLI) or ?debug (browser):

#ModeCLI flag / URL paramDescription
1Mine FieldminesOnly mines. Pure evasion.
2Orb HarvestorbsOnly orbs. Collect them all.
3Collision CourseobstacleCollisionHit obstacles. Track every impact.
4Mine SweepermineCollisionRam mines. Log collisions.
5Chaos ProtocolchaosEverything at once with permanent boost and auto-fire.

Navigate the debug menu with arrow keys or number keys, then press Enter to launch.