Big Typing Quiz: the Game Bible
Fixed Home tiers and learning new mechanics#
Tier is the only solo difficulty control. Old Cadet/Pilot/Ace/Nightmare damage and salvage modifiers no longer affect fixed missions. Earned checkpoints remain available across those retired settings.
Home has 19 fixed tiers across ten five-wave groups. Tier 1 builds from 10 to 20 WPM; Tier 19 builds from 125 to 130 WPM. At group progress p = clamp((group - 1) / 9, 0, 1), set low = 10 + 10p and high = 125 + 5p. Tier t asks for low * (high / low)^((t - 1) / 18), rounded once to whole WPM. This makes each tier the same percentage harder than the previous tier within a group: about 15.1% at the opening and 11.0% at the finale, before rounding. For example, Tier 2 is 12 to 22 WPM and Tier 10 is 35 to 51 WPM. Regular waves alternate the group budget and a 15% boost; bosses use the group budget. A higher-pressure warning appears on the preceding wave summary and on the opening briefing when starting directly on a boosted wave. Event formations do not multiply it. Home ends after wave 50. The final boss review leads to the run summary; no wave 51 checkpoint is unlocked. Older saves beyond wave 50 return to the last earned valid group without removing rewards or history. Other sectors retain their existing tier scales. Home's displayed pace and tier suggestions use this fixed scale. The boss attack clocks are calibrated in actual starter-equipment fights at 95% accuracy, without charges or consumables, so the block's survival pressure follows the scale as well as its workload. Equipment and more accurate play can help a slower player survive.
home-boss-pace.js contains the 19 by 10 attack-period table. Generate it with node tools/check-home-pace-browser.cjs --calibrate: this drives the production boss mechanics, corrects seeded mistakes, measures resulting WPM, and finds the survivable attack period on two seeded readings at each tier and boss group. The shipped lookup is independent of the player's loadout and pace; it does not inspect WPM during a fight or force a loss below a number. The existing telegraphed volleys, hull damage and core phases enforce the pressure. A 4% timing allowance covers reading variants and errors carried from earlier waves. Ordinary waves allow some missed enemies; the boss is the block's pace checkpoint. Home mini-bosses delay their opening volley until 1.25 times the priced reading duration. A starter player at the tier pace can finish them without a mandatory hull tax that would make the following boss require faster typing. Recalibrate after changing starter stats, boss text costs or combat mechanics.
A fresh Service Repeater has zero charge generation and zero capacity. The first Home boss (wave 5) guarantees a Capacitor Repeater, equips it immediately, and offers a paused, skippable charge playback. It earns 0.2 charge per correct character and holds 20. Normal Space types; Shift+Space explicitly discharges, even at a pending separator. Each fresh press can clear another word while charge lasts. Repeats from holding the key do not fire. Red errors block firing.
Target switching is available from the first wave. Another enemy's first letter can switch targets between words, before or after Space. Switching away waives a pending separator without typing credit or charge; returning always uses the current word's first letter. Starting a word commits aim again. A separate safe practice introduces both switching windows, available through the ↔ control and Ability Guide.
No nuke opportunities appear in the first five waves. Nuke slots and explanations remain hidden until discovery. Its first pickup introduces the shared-charge blast and F5 control on a paused card. Charge training does not introduce nukes.
Teaching rule: every new or item-granted mechanic needs a skippable first-use explanation, playback or safe practice, plus permanent review from the Armory Ability Guide or an equally discoverable place. Review never spends resources. The guide replays Charge and Target Relay and explains discovered powers and equipped items.
Equipment comparisons: A compact owned-item grid opens from fitted gear in the Hangar or pre-flight. Hover or keyboard focus previews an item; click or Enter keeps it selected. The selected item and currently equipped item appear side by side, including on phones, with aligned rows in a fixed order for that slot. All relevant numerical effects stay visible, including unchanged values and signed green gains/red losses. Values are build totals for the active mount, including upgrades, fitted gear and gambits. Descriptions live below the numbers. Only the separate Equip/Move/Unequip or Buy & equip button changes gear.
The complete design and reference document for Big Typing Quiz. It is two things at once, on purpose: the specification a developer works from, and the manual a player reads when they want to know what a Railgun actually does. Every number in it is the number the code uses.
About this document#
Who it is for. A developer changing a system, a designer arguing with a balance decision, and a player who has just been told that splash rolls off plating and wants to know by how much. Those three readers want the same facts at different depths, so each system is described in the same order: what it is for, how it behaves, then the exact parameters in a table.
Where the truth lives. The code is the truth, this document describes it. Every table here was read out of the modules under js/, and the derived figures (XP per minute, level costs, characters to kill a word) were computed by running those modules rather than by hand. When the two disagree the code wins and this file is stale: fix it in the same commit that moved the number.
How to read a parameter table. Anything named as a multiplier is applied to a base value rather than replacing it. Anything in milliseconds is a real clock. Positions and speeds in the arcade are FRACTIONS of the playfield and playfield heights per second, never pixels, because what matters is how long a threat takes to arrive and not how big the screen is.
A note on units. One word is five characters, everywhere, without exception (CHARS_PER_WORD in js/util.js). Words per minute means correct characters divided by five, over the minutes actually spent typing. Every WPM in this document is net WPM unless it says otherwise.
Conventions. File paths point at the module that owns a rule. A number in code style is a constant you can find by that name. Sections are anchored, so any heading in this document can be linked to directly.
The other documents. ART_DIRECTION.md covers the art: how it was made, what each family has to look like, and the compositing contracts this document only summarises. WEAPON_IDENTITY.md carries the projectile identity contract in full, including the archetype recipes no shipped weapon uses yet. CLAUDE.md is the guide for AI editors and carries the same reasoning in a different order. FILE_MANIFEST.md describes every file in the repo. FIREBASE_SETUP.md has the Firestore rules. This file is the one that carries the numbers.
The arcade world: Beacon Nine#
Home Sector is a bright orbital shipyard under attack by the Scrap Armada. Pilot its defense ship through fifty waves, break machine formations and bring back enough salvage to make the hangar crew smile. The setting fits the painted metal hulls, cyan docking lights, engine trails and dark planetary horizon.
Ten operations: Launch Ring, Chrome Swarm, Heavy Metal, Ghost Signal, Solar Run, Salvage Alley, Ion Weather, Night Shift, Redline Orbit and Last Light. Each covers five waves and ends with a command ship. Flight Control gives each wave a short dispatch; later groups continue the orbital sweep rather than replaying earlier events. Clearing wave 50 completes Home Sector.
Where to reread: Operations shows the current flight briefing and earned enemy intel. Pause repeats the current dispatch. Arrival cards announce command ships; victory cards show their scans. Wave summaries preview the next operation beat. These are flavor surfaces: their text does not add modifiers or change rewards.
Enemy reading formats: short ships carry words or coherent phrases, and larger ships carry whole sentences bounded to eight words. The independent reading library supplies varied typing practice. Capitalization, punctuation, keyboard eligibility and seeded selection retain their existing rules.
The arcade world guide below documents the setting and its writing rules.
1. The brief#
A complete beginner and a 130 words per minute typist have to open the same page and both find it worth playing.
That sentence is the whole design problem, and almost every decision in this document is an answer to it. It is harder than it sounds, because the two audiences want opposite things:
- A beginner needs short material, visible progress, forgiveness, and a reason to come back tomorrow before they are any good.
- An expert needs material that is actually hard, a number that moves fast enough to respect their speed, and no tutorial standing between them and the game.
Sites that pick one audience are common and they are all the same site. Sites that try to serve both usually do it with a difficulty menu, which fails because a difficulty menu asks a beginner to self-assess before they have any idea what the scale is.
Big Typing Quiz answers it four ways at once, and the four are deliberately different shapes so that no single player can be at the bottom of all of them:
| Axis | What it measures | Shape | Who it favours |
|---|---|---|---|
| Level XP | Real target WPM and accuracy in Starfall; measured run pace in training | Per-target work times pace times squared accuracy | Faster, accurate typists |
| Mastery | Improvement against your own past | Ratio, relative to your own baseline | Everybody equally |
| Scrap | Enemies defeated and objectives cleared in Starfall | Activity value, with salvage bonuses; no WPM or accuracy factor | Every player who clears combat work |
| Gear | Combat survival and salvage | Reduces failure and can improve scrap, never Starfall performance score or XP | Anyone who plays |
And two mechanisms remove the need to self-assess at all:
- The recommended challenge. Personal pace suggests one of 19 fixed tiers. The selected tier determines the workload and starting vocabulary, and stays fixed until the player changes it. All tiers are available.
- The placement. A 60 second Flight Check on the first visit grants the level that performance already deserves, so an experienced typist starts around level 22 instead of at the home row.
1.1 The rules the design will not break#
These are load bearing. Everything else is negotiable.
- Nothing is ever gated. Every game, drill, curriculum node, Academy group, sector and vendor is reachable in the first minute. Progression is shown as a score, a star count or a badge, never as a lock. A sector states a recommended power and lets you in regardless.
- Gear buys access, skill buys rate. Gear reduces failure so you survive deeper. It never multiplies XP.
- Targeting assistance preserves score. Mistake forgiveness, combo protection and earlier reveals in the targeting slot carry no score penalty. Other slots retain their individual tradeoffs.
- Accuracy is paid in time and performance rewards. A word costs its own letters to erase and typing banks charge toward it, so being accurate literally means typing fewer characters. Ordinary targets keep the correct prefix after a mistake, while marked Rewind targets have a capped reset rule. You take the wrong letter back out and the turret holds its shot for a fraction of a second, and the field keeps descending through both.
- Only complete runs count. A run walked out of is discarded everywhere except the per-key model, which keeps the keystrokes because they are still real evidence about your fingers.
- One funnel.
finishRun()injs/run.jsis the only thing in the codebase that grants XP, scrap, cores, badges, loot, bests, History entries or leaderboard posts. A game reports what happened; the funnel decides what it was worth. - Difficulty is priced in WPM and accuracy. The selected numbered tier fixes the typing workload. A separate survival estimate uses the fitted loadout, actual task words and declared accuracy assumptions. Bosses repeat attacks rather than imposing a global WPM deadline.
- Mistake consequences are visible and bounded. Ordinary targets cost correction time, accuracy, heat and combo retention. Clearly marked Rewind and Brood targets can reset or add enemies at most twice. They never remove already banked score or create paid repeat work. Correction stays available.
2. How to play#
This section is the manual. Everything in it is expanded later.
2.1 The first ten minutes#
- The Flight Check offers itself once. One minute of ordinary words. It measures you, grants the level you have already earned, seeds the per-key model with real evidence, and tells the Academy which key groups you have already proved. Declining costs nothing and it never nags again, but the offer stands on the Arcade card and in Progress until you take it.
- Open the Arcade and play Starfall Keys. Craft descend carrying one to three words each. Type a word and it is destroyed. Your typing also banks CHARGE, and SPACE spends that to delete a word you have not typed. Mistype and the wrong letter lands in the box over your base until you backspace it out. Every fifth wave a stationary boss broadcasts a paragraph and exchanges fire with your ship. Your hull and equipment determine how many hits you survive.
- Spend scrap in the Armory. Scrap accrues from time played, so you will have some. Buy a hull or a coolant first: staying alive longer is what makes everything else pay.
- If the keyboard is not yet under your fingers, open Training. The Academy teaches two keys at a time and never scores you on speed.
2.2 Controls#
| Where | Key | What it does |
|---|---|---|
| Arcade | any printable character | Locks the lowest threat whose CURRENT word starts with it, then types into that lock. Typing the word OUT destroys it |
| Arcade | Space | DISCHARGES the charge that typing banked: deletes the locked word without typing the rest of it, or the word closest to your base if nothing is locked |
| Arcade | Space between the words of a held phrase | TYPES THE SEPARATOR. A stack or a line craft is written on screen with spaces between its words and is typed the same way, so the space is a character it ASKS FOR: it counts, it banks charge, it keeps the combo, and it never fires. Skipping it and going straight to the next word is a mistype like any other |
| Arcade | Backspace | Takes back a mistyped character (nothing else is accepted until it is gone), then the letters you got right, and then the target itself: back it all the way out and you are free to commit to anything on the field |
| Arcade | Tab | Swaps to the other weapon hardpoint (only if one is fitted) |
| Arcade | Ctrl + Space | Spends an emergency flare on whatever is closest to your base |
| Arcade | Escape | Pauses during a word, a pending separator, or an error. After Space, it can release the empty target preference |
| Arcade | Enter or Escape while paused | Resumes, through a 3-2-1 countdown |
| Arcade | F8 | Opens the run's event timeline, live or paused |
| Arcade | any key during a wave card | Skips the breather and starts the next wave |
| Boss transmission | Backspace | Removes pending errors before editing accepted text. The input line also has a touch Backspace button |
| Sprint and drills | Backspace | Corrects a character. It restores the text but never refunds the accuracy |
| Anywhere | the tab bar | Six sections: Arcade, Training, Versus, Progress, Leaderboards, Armory |
On a phone the keyboard is raised by a single hidden input (#kbCatch) that the app never rebuilds, so it stays up for the whole run.
2.3 Reading the arcade HUD#
| Readout | Meaning |
|---|---|
| Hull | Base health. At zero the run ends and is scored |
| Heat | Rises on every mistype, bleeds off over time. Full means the turret jams for just over a second |
| Combo | Consecutive correct characters. Drives combat feedback and streak-sensitive weapons, never a score multiplier |
| Wave | The wave counter. Every fifth is a boss (every fourth in the Rift) |
| Score | Performance points from defeated targets: unique typed work, measured target WPM and squared accuracy |
| Charge | The capacitor, in LETTERS, beside what the thing you are aimed at would cost to skip. Green means space will delete it |
| Scrap | Credited combat salvage from kills and cleared objectives, including applicable salvage bonuses |
| Flares | Emergency flares left, if your module carries any |
| Guns | Both hardpoints, with the live one lit. A dashed edge or a ring mark means an area discharge |
| The box over your base | What you have typed of the current word, anything mistyped (in red), and what the meter still owes. Its edge and its typed letters wear the equipped weapon's colours |
| Threat, top centre | The REQUIRED WPM: the pace the field is currently demanding of you. Four pips under it say which severity band that is, and the screen edges carry the same band without being looked at. Section 7.14 |
| Boss attack intention | The next volley, seconds until launch, damage per hit after armour, and escort launches. Boss fights have no required-WPM deadline |
| The strip along the bottom edge | The capacitor again, as pure extent: it fills outward from the centre as charge banks and brightens when a discharge can pay |
Everything from the threat pill down is designed to be read WITHOUT looking at it, because during a wave the eyes belong to the word. Section 7.15 is the whole argument.
2.4 A dozen things worth knowing early#
- Typing the first letter of a word locks the LOWEST threat carrying it. The lowest one is the one about to land, and no two craft on screen ever offer the same first letter, so that keystroke is never a gamble.
- TYPING CHARGES, SPACE FIRES. A word costs its own letters, so the meter has to reach the word before space will take it. Surplus charge carries over. The one place space does not fire is between the words of a craft carrying a PHRASE: that space is part of the phrase, so it is typed like the letters around it.
round laugh trackis eighteen keystrokes. - A craft can carry one to eight words. The count is its size, its damage and the number of times you have to engage it, and the lock is released after each word so you are free to answer something more urgent.
- A wrong key never deducts anything. It lands in the box over your base in red, has to be backspaced out, costs heat and the combo, and stalls the turret for a fraction of a second.
- Power-ups are typed like anything else, then fired on. They are the short words in capitals.
- A boss alternates prose with generators, guard formations and precision weakpoints that require typing. Its announced volleys exchange damage with your hull; it never descends.
- Being accurate with a precision weapon fills the meter half again as fast, so a clean stretch hands you words you never have to type. The letters a discharge saves you are a SNAP SHOT. The kill earns combat salvage; only original characters you actually typed earn performance score and XP.
- Fumbling with a precision weapon banks almost nothing, so the same stretch buys you no free words at all. The word you were typing still dies. Nothing is ever deducted.
- Gambits are opt-in handicaps that pay a salvage multiplier. They are how you make the game hard again after your gear has made it easy.
- Cores only ever drop from bosses and mini-bosses. Nothing else in the game produces one.
- The Academy never measures speed. Certifying the whole keyboard is the largest single XP award in the game.
- Contracts, the streak and the Salvager's shelf all roll over at your local midnight.
- The number at the top of the screen is the WPM the field is asking for right now. You do not have to read it: the edges glow harder as it climbs, and a word with a line struck through it is one that is going to land whatever you do, so spend your typing elsewhere.
Escapewith nothing locked pauses, and pausing costs you nothing at all. The clock the run is scored on stops with it.
3. The loops#
A game that only has one reward cycle only holds one kind of player. Five run at once, and each is a different length:
| Loop | Length | What closes it |
|---|---|---|
| The keystroke | Under a second | A correct character: a rising pitch, a beam, damage |
| The wave | About a minute | Threats cleared, a wave card, a score milestone, an event banner |
| The encounter | Minutes | A boss killed and salvage falling out of it |
| The session | Twenty minutes to an hour | Levels, certifications, badges, a vendor's stock |
| The long game | Weeks | The collection, the diploma, the boards, ascension |
The middle loop is the one most typing sites do not have at all, and it is why the loot system exists: XP is continuous and predictable, scrap accrues by the second, and a badge fires once. None of those produce the moment where something falls out of a boss and you do not yet know what it is.
4. Progression#
4.1 Axis 1: level XP, absolute skill#
Current Starfall runs use performance-v1. Every enemy and boss objective has its own engagement clock. The first observed consecutive-key interval is also credited to the opening keystroke, avoiding inflated WPM on very short words; it is measured from actual input, never seeded from the baseline. It includes mistakes, correction and thinking while selected. Switching away closes that interval; returning resumes the same sample. A wrong acquisition key and its correction time follow into the next target rather than disappearing from reward accuracy. Pauses and protected reading do not advance it. Measured WPM is real correct characters divided by five, per active minute. It does not use paragraph-equivalent effort, the chosen tier, an entered baseline or a gear multiplier.
Each defeated enemy leaves a 2.2-second receipt at its position: large WPM and accuracy figures above an enlarged amber scrap reward. Nearby receipts spread apart and stay inside the field. A perfect result adds a small star. Untyped charge, splash or nuke kills show an unmeasured dash, never invented speed or 100% accuracy; short speed samples are identified. Reduced motion keeps receipts stationary. The latest result also remains in the existing HUD summary.
Target selection uses four small corner brackets without glow. Affordable charge gently changes their tint. A short underline shows remaining words; neither indicator surrounds the whole ship. Area previews share these quiet corners rather than stacking a second targeting outline.
Rewards count each original requested position only once:
rawWpm = correctCharacters * 12000 / activeMilliseconds
rewardWpm = min(260, uniqueCorrectCharacters * 12000 / max(1000, activeMilliseconds))
rewardAccuracy = uniqueCorrectCharacters / (uniqueCorrectCharacters + wrongAttempts)
targetXP = uniqueCorrectCharacters / 5 * rewardWpm / 40 * rewardAccuracy^2
targetScore = targetXP * 10
Only a defeated eligible target pays. Zero measured time pays zero. A charge, splash or nuke pays score and XP only for the original positions the player actually typed before the kill. Reset text, retyped prefixes and mistake-born echoes cannot create new paid work. Raw WPM/accuracy remain available as honest feedback even where the one-second reward floor or 260 WPM reward cap applies. The ledger keeps fractional amounts until the run total is rounded for banking. Optional event clean-clear bonuses now help charge acquisition rather than performance score: the listed 2x or 3x clean bonus affects charge only.
For 100 unique characters at perfect accuracy, a 40 WPM target pays 200 points and 20 XP; an 80 WPM target pays 400 points and 40 XP. At 95% reward accuracy, the latter pays 361 points and 36.1 XP before total rounding. Difficulty, challenge tier, equipment, combo, the Zone, FOCUS, events and ascension add no Starfall score or XP multiplier. There are no flat first-run, first-clear, personal-best, boss, day-streak, comeback or badge XP payments on these runs. Badges, contracts and checkpoints can still record the achievement.
Training, sprints and legacy run formats retain the following pace model. XP counts correct characters produced under pressure, which is what typing skill is. The pace half of a run's XP is:
words = correct characters / 5
paceXp = words * paceFactor(netWpm) * accFactor(acc) * difficultyMultiplier
with
paceFactor(w) = clamp((w / 40) ^ 1.35, 0.35, 8)
accFactor(a) = clamp((a / 100) ^ 2, 0, 1)
Accuracy is squared, so speed bought with sloppiness does not pay: 95% keeps 90% of the XP, 85% keeps 72%, 70% keeps 49%.
The reference pace is 40 WPM, where paceFactor is exactly 1. Because a faster typist also produces more words in the same minute, the compounded rate is steeper than the exponent alone suggests:
| Net WPM | paceFactor | XP per minute at 100% | at 95% |
|---|---|---|---|
| 15 | 0.350 (floor) | 5 | 5 |
| 20 | 0.392 | 8 | 7 |
| 25 | 0.530 | 13 | 12 |
| 30 | 0.678 | 20 | 18 |
| 40 | 1.000 | 40 | 36 |
| 50 | 1.352 | 68 | 61 |
| 60 | 1.729 | 104 | 94 |
| 70 | 2.129 | 149 | 134 |
| 80 | 2.549 | 204 | 184 |
| 90 | 2.988 | 269 | 243 |
| 100 | 3.445 | 345 | 311 |
| 110 | 3.918 | 431 | 389 |
| 120 | 4.407 | 529 | 477 |
| 130 | 4.910 | 638 | 576 |
| 140 | 5.426 | 760 | 686 |
| 160 | 6.498 | 1040 | 938 |
Both clamps matter. The floor of 0.35 keeps a genuine beginner earning something rather than nothing, which would read as broken. The ceiling of 8 stops a freak measurement (a stuck key, a paste, a three character run with a tiny denominator) from minting a level.
Training flat bonuses are the beginner's engine. They do not pay on current Starfall runs. They are fixed amounts, so they dominate the XP of somebody hitting firsts constantly and are a rounding error to an expert:
| Bonus | XP | When |
|---|---|---|
firstRun | 250 | The first completed run of a game |
personalBest | 150 | A new best for this game and mode |
dailyFirst | 200 | The first completed run of the day, any game |
perfectRun | 220 | 100% accuracy on a run of at least 60 characters |
waveMilestone | 60 | Per multiple of 5 waves reached in the arcade |
nodeFirstClear | 200 | First time a curriculum node is cleared |
nodeStar | 120 | Each new star on a curriculum node |
academyCertified | 300 | Each Academy key group certified |
academyGraduated | 3000 | Certifying all 21 groups. The largest flat award in the game |
Defined in BONUS, js/xp.js.
The level curve costs round(60 + 40 * level^1.28) XP for the step from level to level + 1, capped at level 100. The exponent is gentle enough early that a beginner sees levels move in one session, and steep enough later that level 60 means something.
| Level | Cost of the next level | Total XP to reach |
|---|---|---|
| 1 | 100 | 0 |
| 2 | 157 | 100 |
| 5 | 374 | 776 |
| 10 | 822 | 3,508 |
| 20 | 1,911 | 16,457 |
| 30 | 3,170 | 41,115 |
| 42 | 4,844 | 88,209 |
| 50 | 6,041 | 131,112 |
| 60 | 7,613 | 198,527 |
| 80 | 10,975 | 382,270 |
| 100 | (capped) | 635,670 |
Ranks follow the level, so the number and the word never disagree:
| Level | Rank |
|---|---|
| 1 | Cadet |
| 5 | Recruit |
| 10 | Ensign |
| 15 | Pilot |
| 20 | Lieutenant |
| 30 | Commander |
| 40 | Captain |
| 50 | Ace |
| 65 | Wing Commander |
| 80 | Vanguard |
| 95 | Legend |
| 100 | Grandmaster |
4.2 Axis 2: mastery, relative to yourself#
Curriculum nodes and Academy drills are judged against your OWN rolling baseline, never against a fixed target. That is the mechanism that keeps a beginner progressing, and it is the thing most star-based typing sites get wrong: fixed WPM targets give the beginner who needs encouragement most none at all, and let the expert clear everything on day one.
How a node is starred (scoreNode in js/skill.js):
| Rule | Value |
|---|---|
| Accuracy needed to clear the node at all | 92% |
| First cleared run | Earns one star by definition, and becomes the baseline |
| Further stars | Land at 1.05x, 1.12x and 1.22x of your own baseline for that node |
| Third star also requires | 96% accuracy |
| Stars are never taken away | Once awarded, permanent |
| Baseline rise on a better run | 35% of the gap, immediately |
| Baseline fall on a worse cleared run | 6% of the gap |
| Minimum baseline | 8 WPM, so nothing divides by nearly zero |
Because the steps are RATIOS, 18 to 24 WPM (plus 33%) and 100 to 115 WPM (plus 15%) are the same kind of event, and the beginner's is in fact the larger one. The baseline falls at roughly a sixth of the rate it rises, so it is a floor you have to walk back down to rather than a punishment for one good night.
4.3 Axis 3: scrap, combat activity and consistency#
In current Starfall runs, scrap rewards defeating enemies and clearing objectives. It has no WPM, accuracy, time-idling or personal-best factor. Killing more eligible enemies is a useful alternative objective to maximizing performance score. A charged kill can earn its full salvage value even when little or none of its text was typed.
enemyBaseScrap = max(1, round(1 + originalCarriedCharacters * 0.35))
salvageScale = difficulty.salvage * clamp(lootMult, 0.5, 6) * clamp(ascension, 1, 7)
enemyScrap = max(1, round(enemyBaseScrap * salvageScale))
objectiveBase = event: 18; mini-boss: 40; boss: 120 + 40 * completedPhases
objectiveScrap = round(objectiveBase * salvageScale)
The shared helpers in js/xp.js apply the scale when the drop is credited. Combat floaters, the HUD, wave records and the run bank use that exact number; finishRun never multiplies it a second time. Mistake-created enemies and regenerated objectives are not fresh salvage opportunities. Loot conversion, cores and separately settled encounter receipts are itemized separately from the per-wave combat scrap record.
Training and legacy formats keep their older salvageFactor based on measured run speed and accuracy. This rule does not apply to performance-v1 Starfall. Existing currency, items and upgrade levels are preserved.
4.4 Axis 4: gear, access rather than rate#
Stated as one line, because it is the thing to protect if you change anything in js/gear.js:
Gear buys ACCESS. Skill buys RATE.
Almost every stat in the catalogue reduces FAILURE: more health, less damage taken, more heat before a jam, a combo that survives a slip, a charge that swats the thing about to land, a field that descends slower. Good gear lets a 30 WPM player survive to wave 18 in a sector that would have ended them at wave 6, and carries them into the Rift, which is exactly what it is for.
Equipment can increase salvage and change survival. It does not multiply current Starfall score or XP. Former score-only gear bonuses now contribute to salvage instead, preserving those items as meaningful choices. Typing faster and more accurately raises performance rewards directly, while better weapons can clear more enemies and earn more scrap.
The full gear system is section 10.
4.5 The placement#
The Flight Check is the skip-ahead. It runs a 60 second sprint and then grants a lump sum equal to the level that performance deserves.
placementLevel(w, acc) = clamp(round(((w - 18) / 3.6) * (acc / 100) ^ 1.5), 1, 42)
(and exactly 1 below 20 WPM)
placementGrant(...) = max(0, totalXpFor(placementLevel) - currentTotalXp)
| Net WPM | Level at 100% | at 97% | at 92% | XP granted to a new account at 97% |
|---|---|---|---|---|
| 20 | 1 | 1 | 1 | 0 |
| 30 | 3 | 3 | 3 | 257 |
| 40 | 6 | 6 | 5 | 1,150 |
| 50 | 9 | 8 | 8 | 2,149 |
| 60 | 12 | 11 | 10 | 4,330 |
| 70 | 14 | 14 | 13 | 7,400 |
| 80 | 17 | 16 | 15 | 9,973 |
| 90 | 20 | 19 | 18 | 14,664 |
| 100 | 23 | 22 | 20 | 20,398 |
| 110 | 26 | 24 | 23 | 24,822 |
| 120 | 28 | 27 | 25 | 32,392 |
| 130 | 31 | 30 | 27 | 41,115 |
| 160 | 39 | 38 | 35 | 70,275 |
| 180+ | 42 (cap) | 42 | 40 | 88,209 |
Four rules make it safe to re-take:
- It only ever raises. The grant is the difference between what you have and what the measurement is worth, floored at zero.
- The profile keeps the BEST placement, not the latest. Rewriting "placed at level 22 from 96 WPM" with a slower re-take would replace a true sentence with a misleading one.
- It seeds the per-key model, and that seeding also only ever raises. This is what makes the curriculum immediately recommend symbols and code to a proficient typist rather than the home row.
- It hands the Academy a head start. Any key group whose keys the measurement already proved (12 or more samples at 90% or better) is marked as evidence rather than starting from zero.
The dialog offers itself once per profile and then never again. placed records a completed check; placeOffered is stamped the moment the dialog opens, because closing it is an answer and re-opening a modal on every load until a one minute test is finished is nagging. The offer itself does not go away: the Arcade's "Start here" card and the Progress re-take button stand for as long as the profile is unplaced. tools/check-app.py guards this.
4.6 Per-game ranks#
Each mini-game keeps its own XP, rank, plays, bests and badges in state.games[<id>], so playing Starfall levels up Starfall AND the account, and being level 40 does not make a game you have never opened look finished.
The per-game curve is flatter than the account one, at round(120 + 70 * rank^1.15) per rank, capped at rank 50. A game rank is a familiarity measure, not a skill measure.
| Game rank | Cost of the next | Cumulative |
|---|---|---|
| 1 | 190 | 0 |
| 5 | 566 | 1,298 |
| 10 | 1,109 | 5,191 |
| 20 | 2,314 | 21,602 |
| 50 | (capped) | 149,110 |
4.7 Ascension#
At level 100 you may reset the level and XP, keeping EVERYTHING else: gear, cosmetics, badges, the Academy, History, the boards. Each ascension grants a permanent +6 percentage points per ascension to training XP and salvage, a visible mark, and 5 cores. The account multiplier is 1 + 0.06 * ascensions, an additive increase. Starfall combat salvage caps this ascension component at 7x; its performance score and XP are never multiplied.
It is opt-in and never suggested twice. It exists so the ceiling is a door rather than a wall.
5. Measurement#
5.1 Speed and accuracy#
There is exactly one definition of each, in js/util.js, and every surface in the app uses it:
CHARS_PER_WORD = 5
wpm = (characters / 5) / minutes
netWpm = (CORRECT characters / 5) / minutes
accuracy = correct / typed * 100
What counts as a minute. Current Starfall performance tracks manual enemy, boss-objective and optional asteroid typing. Pickup-code typing is outside this performance ledger. Selected-target thinking, mistakes and correction time count; pauses and protected cards do not. The first contiguous pair of keys supplies one observed opening interval for that engagement, avoiding short-word inflation without a baseline seed. Unassigned acquisition errors and their time follow the next target; remaining errors stay in an unpaid measurement row. Target switches suspend one sample and returning resumes it. Original positions earn at most once, while raw correct/typed counts remain available for feedback. History retains the actual play duration separately from target engagement time.
Feedback beside the ship. The left segmented meter shows hull percentage, HULL LOW at 50%, and CRITICAL at 25%, with shield points underneath. The right meter shows stored charge, a notch for the next shot's cost, and FIRE READY when the shot is affordable. During transmissions it says AUTO FIRE; overheating says JAMMED. Hull threshold crossings play a short descending warning once per crossing. The existing charge pitch and full-capacity cue continue; the meters remain readable with sound or motion disabled.
Power-up dock. The five fixed F1–F5 slots sit below the playfield beside the lower controls. Filled slots say Ready, active effects show seconds and a draining strip, and overflow drops retain their waiting timer. Collection cues travel toward the matching lower slot. The help button remains beside the slots.
Boss word goals. Each phase forecasts the final full word using the active weapon, current damage modifiers, upcoming combo bonuses and remaining health. The last word is underlined and words beyond it are dimmed. The endpoint updates after mistakes, corrections and weapon changes. In fixed-tier fights, maximum core health stays at its initial text length times sector HP scaling: mistakes never inflate it. Current health clamps at zero, but the current word must be completed before the phase clears. Legacy fights retain upward whole-word health alignment. Retyping already-scored letters still deals no duplicate damage. If the remaining passage cannot finish the phase, its text stays undimmed and the label explains that another passage may be needed.
Asteroid pace. Optional rocks travel at no more than 35% of the slowest solved enemy speed in the wave. Their longer lifetime leaves time for spare-time collection; they still do not hold a cleared wave open.
Backspace. It is allowed everywhere: in the arcade it takes back the red, then the letters you got right, then the lock itself, and in sprints, drills and boss transmissions it corrects the text, because a paragraph you cannot correct is a punishment rather than a test. Everywhere it restores the text and the character counts toward net WPM, but the keystroke that got it wrong stays in the accuracy denominator forever. That is the standard convention and the only one that stops "type garbage and repair it" scoring 100%.
5.2 The plausibility gate#
plausible() in js/run.js drops a run that claims something no human did. The Cloudflare Worker holds the same line independently on the leaderboard side.
| Rule | Value |
|---|---|
| Must be complete | complete: false is discarded everywhere |
| Minimum duration | 2,000 ms |
| Must have typed something | typed > 0 |
| Correct cannot exceed typed | enforced |
| Maximum net WPM | 260 |
5.3 The per-key model#
One record per character and per typed pair, holding how often it was hit, how often it was right, and how long the finger took. This is what makes the site personal: the arcade builds asteroid chunks from your worst keys, the drills target them by name, and the Progress heat map is a picture of your own hands.
| Parameter | Value | Why |
|---|---|---|
MIN_KEY_MS | 40 ms | Faster than this is a paste or a stuck key, not a finger |
MAX_KEY_MS | 3,000 ms | Slower is somebody answering the door |
KEY_INERTIA | 0.25 | Old evidence is discounted, so the model notices improvement |
KEY_ALPHA_MIN | 0.1 | A veteran's record stays movable |
KEY_CAP | 140 characters | The blob syncs and has to stay small |
BIGRAM_CAP | 400 pairs | Same |
MIN_KEY_SAMPLE | 12 presses | Before a key may be called weak |
ACC_FLOOR | 70% | As bad as the accuracy scale goes for one key |
LAT_SLOW / LAT_FAST | 1.8x / 0.6x | Of your own average pace, scoring 0 and 100 |
DEFAULT_REF_MS | 320 ms | Stand-in pace before you have been measured |
Timing is keyed by the character that was EXPECTED, not the one that was pressed: knowing you typed k when l was wanted says something about l.
Account-level skill is an exponential moving average of recent runs rather than a best, because a best is a story about one lucky minute and this number has to decide what to put in front of you next. The first run is adopted whole (a new player should see a real number, not a fifth of one), then the weight falls off as runs accumulate and settles at a floor of 0.12 for a veteran. Run length weighs in as well: a 15 second burst is not the same evidence as a five minute haul (RUN_FULL_MS is 30 seconds).
5.4 Word tiers#
The lexicon is ten tiers of hand-written real words, each staying strictly inside its own character set, plus a prose tier for sentences and paragraphs.
| Tier | Name | Characters | Words | Chosen at |
|---|---|---|---|---|
home | Home row | asdfghjkl; | 69 | under 18 WPM |
homeTop | Home and top rows | + qwertyuiop | 411 | 18 to 27 |
letters | Every letter | a to z | 332 | 28 to 37 |
common | Common words | a to z | 681 | 38 to 51 |
commonLong | Longer words | a to z | 767 | 52 to 67 |
capitals | Capitals | + A to Z | 201 | 68 to 84 |
punct | Punctuation | + ,.;:!?()-'" | 166 | 85 to 99 |
numbers | Numbers | + digits and .:/-,$% | 127 | (by node) |
symbols | Symbols | letters, digits, @#$%^&*_+=/<>~-! plus the vertical bar and the backtick | 118 | (by node) |
code | Code | everything | 183 | 100+ |
prose | Prose | sentences with punctuation | 371 | (by node) |
Plus 20 quotes for the sprint's Quote mode.
tierForWpm in js/xp.js maps the rolling skill number onto that ladder, and it is the single reason the same arcade game is right for both audiences. Difficulty and sector multiply on top; they never replace it. A fixed table would mean picking an audience.
The "(by node)" tiers are DRILL material and only ever appear where a player asks for them by name (a curriculum node, an Academy group, a sprint mode). The arcade's falling craft label themselves from ARCADE_TIERS in js/words.js, which is this ladder with punct, numbers and symbols removed: fragments, part numbers and bare operators drill exactly what they claim under a node, and read as the game speaking garbage when they rain out of the sky (see 7.2).
5.5 Weak keys#
weakKeys(n) returns the worst keys the model is confident about (at least 12 presses), scored on accuracy first and latency second. They are used for:
- asteroid chunks in the arcade, which are the one thing in the game that is not a real word, because there is no English word made of the four keys you are worst at (and which are OPTIONAL: see 7.8),
- the Salvage Window event, which offers more of them,
- the Hive Mind boss, which speaks in them on its last phase,
- the sprint's Weak keys mode and the curriculum's two adaptive nodes.
Your weak keys are the one thing a Versus room does NOT share.
6. Modes and surfaces#
The app is six tabs. Two of them hold games, the rest are where the game is read, spent and compared.
| Tab | Id | What is there |
|---|---|---|
| Arcade | arcade | The game hub (one card per registry entry) staged as an operations deck with a holographic sector chart, the pre-flight briefing, and the running game. The hub and the game are never on screen together |
| Training | train | The Academy on top, the curriculum (Flight School) below |
| Versus | versus | Head to head racing, signed in with a claimed name |
| Progress | progress | What is open today, how good you are now, what you have done |
| Leaderboards | ranks | Eight opt-in boards |
| Armory | armory | Staged as a hangar bay: the loadout first (it is the point of the economy), then the three vendors, then cosmetics and the collection |
The router mirrors the active tab into location.hash, so app.html#arcade deep-links. The ids are stable even if a caption is renamed.
6.1 The game catalogue#
js/games/registry.js is the only list of what games exist.
| Game | Id | Status | Modes | Board scope |
|---|---|---|---|---|
| Starfall Keys | starfall | Live | One mode (Endless), four difficulties | Difficulty, plus a deepest-wave board |
| Flight Check | sprint | Live | 60 seconds, 15 seconds, Quote, Weak keys | The two timed modes |
| Wing Command | coop | Announced | Pair, Squadron | (none yet) |
| The Forge | forge | Announced | Worst five, Transitions, The ladder | (none yet) |
| Terminal | terminal | Announced | Tokens, Lines, Symbol storm | (none yet) |
An announced card is not a lock. It is a game that has not been BUILT yet, it says so in those words, and it must never show a padlock, a level or any implication that the player fell short. Nothing on this site is gated; the only thing standing between a player and an announced game is us.
7. Starfall Keys#
The flagship. Your base sits on the lower edge, craft descend carrying words, typing a word destroys the thing carrying it.
7.1 The field#
Simulation units are FRACTIONS of the playfield, so nothing depends on canvas size and a phone turned over mid-run never moves a threat relative to the base.
| Constant | Value | Meaning |
|---|---|---|
STEP | 1/60 s | Fixed timestep, with an accumulator, so a 144Hz laptop and a throttled phone run the same simulation |
MAX_FRAME | 0.25 s | The most a slept tab may catch up in one frame |
VIEW_TOP | -0.04 | The sliver above the field a craft drifts in from |
REVEAL_TOP | 0.12 | The deepest strip the reveal stat can buy |
BASE_Y | 0.93 | A threat at or past this has arrived |
MAX_PARTICLES | 260 | Decoration ceiling |
Descent speed is in playfield heights per second, which is the honest unit: what matters is how long a threat takes to arrive.
7.2 The wave cycle#
preflight (2200 ms, skippable after 700 ms)
-> waveActive: the plan is played back, threats descend, encounters fire
-> waveSummary when the MANDATORY work is done (an ignored opportunity never
holds a wave open), 4200 ms, skippable by a key, hull regen applies
-> bossIntro on a boss wave: frozen, protected, left by a deliberate action
-> bossActive: the fight
-> bossVictory: frozen indefinitely, the reward already banked
-> next wave
Those are the ENCOUNTER STATES, and every system consults the same policy about them. preflight, waveSummary, bossIntro, bossVictory and runOver are FROZEN: the simulation does not step at all, so heat does not decay, an event does not expire, a buff does not drain, the pace denominator does not grow and no time-based reward accrues. That is implemented in the CLOCK (now() returns the instant the freeze began) rather than as a flag each system has to remember, which is what makes an indefinite reward review safe: reading it for ten minutes is worth exactly nothing.
7.2a One difficulty contract#
Typing context changes are protected. Boss phases, formations, shield commands, replacement passages and returns from a mini-boss show a 1.2-second reading cue with a countdown sound and a distinct ready sound. The next prompt is visible, but combat, timers, heat and scoring stay frozen. Trailing keys, Backspace, charge attacks and other combat shortcuts are discarded. Solo play also waits for 220 ms without input; Versus uses the fixed duration. A held key must be released before its repeat can type into the next prompt. A partially typed field target disappearing through escape or incidental damage uses the same protection. Completing an ordinary word still allows immediate targeting.
On desktop viewports at least 1,000 px wide, fixed-tier bosses keep their battlefield on the left and phase instructions and transmissions on the right. The current action stays visible; Phase help contains the full rules and recovered transmission. The finish-word cue appears above the passage, and expiring mechanic notices never move the reading surface.
A numbered challenge is fixed, not adaptive. js/games/challenge.js is the shared source for preview, ordinary waves and bosses. Personal pace suggests a tier once, then settings.challengeTier remembers the selection. A faster run, new Flight Check, manually entered baseline or stronger weapon never changes the selected tier's workload. All 19 tiers are open from the start.
These are internal paragraph-equivalent scheduling ratings, not minimum measured typing speeds. Mission selection displays loadout survival estimates.
| Tier | Internal wave rating | Internal boss rating |
|---|---|---|
| 1 | 8 | 9 |
| 2 | 12 | 13 |
| 3 | 18 | 20 |
| 4 | 25 | 28 |
| 5 | 32 | 35 |
| 6 | 40 | 44 |
| 7 | 50 | 55 |
| 8 | 60 | 66 |
| 9 | 72 | 79 |
| 10 | 85 | 94 |
| 11 | 100 | 110 |
| 12 | 115 | 127 |
| 13 | 130 | 143 |
| 14 | 150 | 165 |
| 15 | 175 | 193 |
| 16 | 200 | 220 |
Mission selection recommends the highest tier whose upper loadout survival estimate fits the player's measured pace. With no evidence it uses the existing 30 WPM fallback. The four ordinary stages use 0.88, 0.92, 0.96 and 1.00 of nominal WPM, with interpolation for a different boss cadence, then the current formation's pressure factor. Boss target = round(nominal WPM * 1.10). Demand does not secretly increase with depth. Cadet, Pilot, Ace and Nightmare remain combat settings for hull pressure and salvage, separate from the numbered typing workload. Sector still determines enemy composition and boss cadence.
Every tier button and the selected mission show estimated survival WPM for the equipped loadout at 95% accuracy, without charge attacks. Each button forecasts the checkpoint it will launch; the boss is included in the block estimate and also shown separately from full hull with a reserve. Slower players can succeed; the estimates are not minimum speeds. A planner creates real words, schedules arrivals and checks deadlines with the shared effort model before a wave starts. Movement remains subject to the universal readable vertical speed cap.
The work model (js/games/typing-effort.js):
Cj characters still required in segment j, separators included
rj that segment's typing rate as a FRACTION of ordinary prose
W sum(Cj / rj) paragraph-equivalent characters
A acquisition still owed, in seconds, charged ONCE per engagement
T(B) = 12 * W / B + A seconds
The reciprocal is the point: a random-letter rate of 0.50 means twice the typing time, not half the workload. CONTENT decides the rate; PRESENTATION decides the acquisition. They are independent, which is why a multiword weak-key target is still weak-key content that merely amortises one acquisition over several words.
| Content | Rate | Presentation | Rate | Acquisition | |
|---|---|---|---|---|---|
prose | 1.00 | paragraph | 1.00 | 0 s | |
words | 0.95 | line | 1.00 | 0.15 s | |
glyph | 0.80 | stack | 1.00 | 0.25 s | |
code | 0.72 | token | 0.95 | 0.35 s | |
random | 0.55 | mote | 0.95 | 0.35 s | |
drill | 0.52 | chunk | 0.95 | 0.40 s |
Hidden text (a Blackout) adds HIDDEN_REVEAL_S (0.45 s) to the acquisition and nothing else: stacking a rate penalty on top would charge twice for one mechanic. A vocabulary tier can modify the rate (punct 0.90, numbers 0.88, symbols 0.85, code 0.85), and it is an AUTHORED TAG rather than an inference from the string.
At B = 60: five characters of prose take 1.00 s, a five-letter isolated word takes 1.46 s, five characters of weak-key rock take 2.42 s.
The loadout survival estimate is a separate model. js/games/loadout-demand.js simulates three representative blocks, using actual task words and authored boss text, at 95% accuracy. It includes fitted hull, shields, damage reduction, field slowing, reveal, assists, heat, turret delay, optional charge use, splash, repairs and boss mechanics. Mission-selection advice sets useCharges:false for both ordinary enemies and boss formations. Each fitted hardpoint is evaluated as a complete strategy; their stats are not added together. In the charge-enabled model one sample allows an overlapping area target. Home uses its calibrated attack clocks and a 5% model hull margin. Other sectors reserve 20% hull plus one complete authored boss attack cycle across all three samples, including spaced, random and paired core mistakes. It starts from full hull and shield with no optional pickups.
The range is the minimum and maximum model threshold across those samples, not a statistical confidence interval or a guarantee. The fresh-hull boss estimate is separate; arriving damaged can raise the required pace. The model allows some enemies through, so clearing every enemy requires more typing. Random events, mini-bosses, positioning, charge timing and manual target choices can change survival. Exact player behavior and every combat interaction are not simulated. The assumptions and limits are available in the collapsed Equipment survival estimate. This secondary estimate never replaces Home's fixed pace in the briefing, slider or tier recommendation. A 30 WPM reference suggests Home Tier 5 in the opening group, irrespective of equipped gear.
Vocabulary starts from the fixed nominal tier and advances one language rung per six waves, within ARCADE_TIERS. It never substitutes a player's newly improved rolling speed into the same selected challenge.
7.2b Where a run may open: checkpoints#
A run opens on wave 1, or on the start of a block whose preceding boss was ACTUALLY DEFEATED, in this sector, at this combat difficulty and numbered challenge tier. Nothing else.
In solo play, continuing from a full boss victory restores hull to 100%, just as restarting at that earned checkpoint would. Service happens on Continue, after the fight result is recorded; it grants no shield, score or salvage. Mini-bosses and Versus do not receive this repair. Defensive equipment still matters within each five-wave block and during every boss fight.
start points = 1 + n*K, where K is the sector's boss cadence (5, or 4 in the Rift)
authorisation = a saved clear, never a formula and never a highest-wave number
| Situation | Restart |
|---|---|
| No boss defeated; death on wave 4 or on boss 5 | 1 |
| Boss 5 defeated; death on waves 6 to 10 | 6 |
| Boss 10 defeated; death on the wave 15 boss | 11 |
| Boss 15 defeated | 16 becomes available |
| Rift boss 8 defeated; death on boss 12 | 9 |
Because a start point is 1 + n*K it is never a multiple of K, so a normal start can never land ON a boss wave. That is arithmetic rather than a check. A harder difficulty's clear also opens the equivalent checkpoint at every EASIER combat difficulty; the reverse never happens. Checkpoints are shared across all threat tiers, including existing tier-specific and legacy adaptive clears. Changing threat tier preserves the selected unlocked start. The cadence is stored WITH the progress, so a later change to a sector's boss spacing conservatively drops back to wave one rather than reinterpreting a clear that was earned against a different schedule.
A DIRECT BOSS START EXISTS ONLY AS A DEVELOPER HOOK. opts.devWave bypasses the resolver so tools/check-app.py can reach a boss without playing four waves first, and it is deliberately a DIFFERENT KEY from startWave: no panel writes it, no saved setting carries it, no URL reads it and normalizeState knows nothing about it, so there is no path from a player's own configuration to one.
This replaced an arbitrary stepper from 1 to 50 with a row of boss-wave presets. Two things were wrong with it: it offered the boss itself, which skips the block that gives the fight meaning; and nothing proved the player had ever been there, so wave 45 was one click away on a profile that had never cleared wave 5. Account level now opens destinations, while checkpoints determine where a run in an open sector may resume. All threat tiers remain available.
Only a defeated boss grants a clear. A surviving hull keeps the fight going. Losing all hull ends the run on that wave without unlocking the next checkpoint; a boss shot absorbed by shields never ends the attempt.
Checkpoint attempts compare individual waves. Current performance-v1 Starfall grants real target performance XP and combat scrap on a checkpoint attempt. Every cleared wave can set its own score and scrap best and enter its exact sector/difficulty/tier/rules leaderboard. Clearing wave 16 from a saved checkpoint is comparable with clearing that same wave after flying the earlier block. An unfinished wave, including an unfinished boss, cannot set a wave record. A later death preserves earlier clears when the finished run banks.
Checkpoint attempts still do not fabricate lifetime depth records or depth badges for waves skipped. Abandoning a run banks no new wave records. Manual personal pace only recommends a tier, so it no longer invalidates a fixed-tier wave record. Legacy adaptive run formats keep their original ranking gates.
Completed is not reached. wavesCompleted is counted as waves end; result.wave is the wave the run was standing in when it finished. The old result.wave - startWave + 1 counted a reached-but-failed wave, so every depth-shaped reward paid for one wave that never happened.
js/games/starfall.js reports meta.startWave, meta.wavesCompleted and meta.checkpoint as plain facts and decides none of the pricing; js/run.js prices it, exactly as it prices everything else. The pre-flight, the launch button, the chrome bar above the game, the opening card and the end screen all say so, so the trade is never discovered afterwards.
7.3 Enemy catalogue#
TYPING A WORD OUT DESTROYS IT, always, with no trigger and no meter involved. That baseline is deliberate and must stay true. The capacitor is what the typing EARNS on top, and a discharge costs the target word its own letters.
A craft carries ONE TO THREE WORDS, and the count is the craft. It sets the size drawn on screen, the damage on arrival (per word still carried), and the number of separate engagements it takes to clear. Fewer, heavier things arrive than used to, because each one is now worth looking at.
Presentation, and why a character is not a character#
Twenty letters of prose, twenty letters arriving one isolated word at a time, and twenty letters of the six keys you are worst at are three different amounts of work. The game used to count them as one, which is why a meteor shower could fill the screen while the threat meter read CALM.
PRESENTATION is the property that fixes it. It is not the word list and not the length, it is whether a reader can see and parse what is coming.
| Presentation | Rate | Acquisition | What it looks like | Carried by |
|---|---|---|---|---|
paragraph | 1.00 | 0 s | A boss transmission, on a surface you are already reading | Boss, mini-boss |
line | 1.00 | 0.15 s | A whole sentence, visible at once; target choice after each word and Space | Elite, Carrier |
stack | 1.00 | 0.25 s | Two or three words, all visible; target choice after each word and Space | Courier, and ANY craft carrying more than one word |
token | 0.95 | 0.35 s | One word, alone, with nothing to read ahead into | Any craft carrying exactly one word |
mote | 0.95 | 0.35 s | A glyph or two, in numbers | Mote (a carrier's swarm) |
chunk | 0.95 | 0.40 s | Weak keys in a row, no language under it | Asteroid |
Presentation is now a RATE and an ACQUISITION rather than a single effort multiplier, and the difference matters in three places. A multiplier is a statement about score; a rate divides into seconds, which is what a schedule needs. Most of what makes a lone short word expensive is FINDING it, which is a fixed cost per engagement that does not scale with length, so folding it into a per-character weight priced a four-letter drone and a fourteen-letter one identically per character and kept charging it to a target the player was already holding. And the old multiplier was applied twice, alongside a blanket 0.8 discount in js/xp.js whose justification was the same acquisition.
paragraph and line are the reference at 1.00 because prose is what typing speed is measured in everywhere else on this site, and because meaning is what lets a reader run ahead. The numbers come out of the reading literature (Salthouse 1986 measured about 174 ms a character for prose against 178 ms for the same words in random order, and random letters far slower than either) and were re-derived here from acquisition cost per engagement over the character counts this game deals. Two independent derivations agreed inside 8 percent.
The counter-intuitive result the design turns on survives unchanged: A STACK IS CHEAPER PER CHARACTER THAN A LONE WORD, because the overhead is the acquisition and not the letters. Stacking is the forgiving rung, and the way to make the game harder is to issue words ALONE.
The SCORE still uses a single weight per character, because a keystroke that cost twice as much is worth twice as much and the player who takes the hard target should be paid for it. That weight is now DERIVED from the model above (effortOf: the rate turns real characters into paragraph-equivalent ones, and the acquisition is converted at the reference pace of 40 WPM), so score and schedule cannot drift apart. It comes out within a tenth of the hand-written table it replaced: a sentence 1.02, a phrase 1.12, a lone five-letter word 1.34, a two-glyph mote 1.90, a five-character rock 2.29. Effort is still deliberately NOT spent on XP or scrap: js/run.js prices those off measured pace and real characters, and a multiplier there would be a way to earn faster by choosing what to shoot at.
A STACK IS CHEAPER PER CHARACTER THAN A LONE WORD, and that is the counter-intuitive result the whole design turns on. The overhead is the ACQUISITION, not the letters: find it, read it, commit, type, let go. One acquisition spread over twelve characters beats one acquisition per four and a half. So stacking is the forgiving rung, and the way to make the game harder is to issue words ALONE rather than to pile them up.
Effort is spent in exactly three places, and deliberately not in a fourth:
- The threat meter.
threatCharsreturns effort characters, so Required WPM stops lying about a field of rock. - The score. A word is worth
6 per letter times its effort, so the keystroke that cost twice as much is worth twice as much. - The wave budget.
fillQueuespends effort rather than counting hulls, so a wave that rolls several rocks sends fewer things. That is the whole of the "spawn meteors less often" rule and it needs no special case: anything expensive to read is automatically rarer in the same proportion, including whatever is added later. - NOT XP or scrap.
js/run.jsprices those off measured pace and real characters. A multiplier there would be a way to earn faster by choosing what to shoot at, and it would break the one sentence the economy rests on.
| Threat | Radius | Words | Presentation | Damage on arrival | Word length | Kill score | Spawn weight | From wave | Special behaviour |
|---|---|---|---|---|---|---|---|---|---|
| Drone | 14 | 1 | token | 6 a word | 3 to 6 | 0 | 3 | 1 | One contextual word |
| Courier | 21 | 2 to 3 requested | stack | 9 a word | authored phrase | 30 | 7 | 1 | A meaningful phrase, typed continuously |
| Cruiser | 24 | 4 to 6, adapted | line | 11 a word | authored sentence | 40 | 5 | 3 | A whole sentence from the current scene |
| Asteroid | 19 | 1 | chunk | 0 (it is an OPPORTUNITY, see 7.8) | 3 to 6 | 25 | n/a | 2 | Not in the mandatory pool at all. Its one "word" is a chunk built from YOUR weakest keys, it carries a reward chosen when it was planned, and it damages the ship only by physically hitting it |
| Splitter | 17 | 1 | token | 7 a word | 4 to 6 | 20 | 3 | 4 | On death by typing or splash it becomes two drones at 1.35x speed |
| Elite | 30 | 5 to 8 | line | 7 a word, bulk 3 | 2 to 9 | 90 | guaranteed | 2 | Carries an INTACT SIGNAL out of js/lore.js. Drops salvage |
| Carrier | 34 | 5 to 8 | line | 8 a word, bulk 3 | 2 to 9 | 140 | guaranteed | 4 | An elite with 3 to 5 motes flying formation around it |
| Mote | 9 | 1 | mote | 3 | 1 to 3 | 8 | never queued | any | A shard of a word. Only ever arrives with a carrier |
| Power-up | 13 | 1 | token | 0 | the capital word | none | (dropped) | any | Falls at 0.55x speed. Missing one costs nothing |
The elite, the carrier and the swarm#
AN ELITE IN EVERY WAVE FROM WAVE TWO, drawn before anything else so it comes out of the wave's budget rather than on top of it. A sentence craft is not a rare event to be lucky into: it is the rung of the effort table that makes the other four legible, because you cannot feel that a lone word is expensive until you have just read forty characters of prose off one hull in the time three drones would have taken. From wave 4 there is a 35% chance it is a Carrier instead.
It carries an INTACT SIGNAL, not a bag of words. signalWords in js/words.js takes a real line out of the SIGNALS corpus in js/lore.js, whole, and returns its words with the authored spelling intact. Every sentence has proper capitalization and punctuation, including names and possessives. The word tier limits its base letters; it never strips sentence capitals or full stops. The planner counts every character actually typed. Short enemies also mix scene words with coherent abstract concepts such as 'free will' and 'burden of proof'. They need not advance the story, but their words must belong together.
NO SENTENCE, NO SENTENCE DISCOUNT. Gameplay includes short authored sentences inside the two restricted keyboard tiers. A missing whole sentence becomes one tier-valid word, drawn and priced as a token. Couriers likewise use a complete phrase or one word. No multi-word fallback assembles random vocabulary.
THE SWARM FLIES IN FORMATION AND SCATTERS. A carrier's motes hold a station on a ring around it, so they arrive WITH the sentence and compete for the same seconds of attention, which is the entire point of the pairing: one craft is asking you to read and the things around it are asking you to aim. When the carrier dies the link drops and every mote falls from wherever it was. Their positions are a pure function of the shared clock and their own seeded station, never of wall time, so both renderers and both clients in a race agree.
A mote carries a SHARD, not a short word (shardFor in js/words.js): one to three characters sliced out of the middle of a real word. A short word is still a word, with a shape your eye knows; a shard has none, which is what the 1.60 is pricing. It also cannot be done with wordFor, because several tiers have no words that short and would answer with the same fallback entry every time.
bulk caps what a word count does to size and damage. Those are driven by words carried, which is right at one to three and absurd at eight: a sentence craft would otherwise be drawn three times the size of a cruiser and hit for eight times a drone. Elites and carriers cap at 3. The real length still shows up in the health ring and in the threat meter.
ONE CRAFT IS ONE STRING. A multi-word craft presents an intact phrase and asks for Space between its words. Starting a word commits aim through that word; after Space, another ship’s initial switches targets. A craft reading crab mint is nine keystrokes including the space, and the space bar in the place a reader's hands already go to put a space types the separator instead of firing the capacitor.
That is why the presentation table above is a consequence rather than a choice: token is reached only by a craft carrying ONE word, and anything carrying more is a stack. A multi-word craft that revealed its words one at a time hid half of what it was asking for (nothing to read ahead into, which is the entire saving a phrase is priced on) and dropped its lock at the boundary, which left the space bar there as the trigger. It also means a held craft's later words are never re-rolled for a first-letter collision, because they are never acquired by their first letter and swapping one out under the player's eyes would be the game cheating.
The held lock is a PREFERENCE and not a commitment, though, and that is what keeps it from becoming a trap. Between words there is nothing typed into it, so the box over your base is empty: its own next letter is answered first (which is the whole point, and the reason its words need no collision check), while any other letter on the field takes you straight there and backspace lets go of it outright. Reading a sentence through is what the held lock is FOR; it is not a claim on the next thing you press.
Both renderers draw a held craft the same way: its words sit at full size on ONE line, the cleared ones staying in place and going faint, which is the progress readout (a courier coming apart left to right needs no other one). The commit key is lit on the FIRST word only, because after that the lock is held and no key is choosing anything.
The radius grows 30% per word carried and shrinks back as words are stripped. Word length ranges are multiplied by the wave's wordLen and by any live event's length multiplier, then floored at 2. Arcade words are always SOLID (no spaces), because space is the fire control: the lexicon keeps its phrases for the drills and the sprint, and the arcade picker filters them out.
A PHRASE IS TYPED AS A PHRASE, SPACES AND ALL, and that is the one place the fire control stands down. A stack or a line puts several words on one craft, with target choice after each completed word and Space, written the way a phrase is written anywhere. So the separator is a character the craft ASKS FOR: round laugh track is eighteen keystrokes, not sixteen.
THE LABEL IS A WINDOW, AND THE REST IS A NUMBER. A craft does not print its whole string. It prints the word under your caret in full, then whole following words while they fit, then an ellipsis, then the keystrokes it still owes you:
face-to-face part-time 22 two words and their separator, all shown
high-speed full-time... 34 more behind, and 34 keys left to kill it
signal one word: the word is already the cost
That number is the threat's LIFE in the unit you pay in. It counts the rest of the word you are typing, every word behind it, and the separators between them, and it ticks down as you type. It is the fastest way to compare two craft when you are choosing which to commit to, and it is why an eight word elite no longer draws a label wider than a phone. Words you have finished leave the line, so the word under the caret stays centred on the craft and the caret never jumps.
That space used to reach the discharge and spend the capacitor to delete the NEXT word of the phrase being read, which looked exactly like the space having been accepted; when the meter could not pay it fizzled instead, so one keystroke did two different things a word apart. The two meanings are now split by what is being ASKED FOR, a question the simulation can always answer:
| Where the space is typed | What it does |
|---|---|
Between the words of a held phrase (sentenceGap) | Types the separator. Counts as typed and correct, banks charge at the weapon's rate, keeps the combo, records original typed work for its target. Never fires |
| Inside a word, or with nothing locked, or on any craft carrying one word | DISCHARGES, exactly as before |
| Between the words of a held phrase with red in the box | Joins the correction buffer like any other key. An uncorrected mistake owns the keyboard, and handing the trigger back there would be the original bug reached from the other side |
Because the separator is asked for, missing it is an ordinary mistake and is paid for in the ordinary way: the next word's first letter lands in the correction buffer to be taken back. After Space, the next word is a fresh target choice. Type its initial to continue, or another ship’s lit initial to switch. Completed words remain removed when you return. A started word holds aim; Backspace can erase its prefix to release it. Escape pauses without throwing away that word.
The separators are counted in threatChars, so the threat meter's Required WPM prices the keystrokes the field is really going to ask for. They are recorded in the per-key model exactly as the sprint records them (the space itself yes, bigrams across it no), and skill.js's drillable() keeps the space bar out of the weak key list while the Progress heat map still draws it.
Every kill drops SCRAP, and the amount pops out of the wreck where it died: 1 + 0.35 per letter the craft was carrying, rounded, before the run's salvage multiplier, which js/run.js applies at the end like every other reward.
Home fleet and visible damage. Painted Fleet is the default art setting, also available through Settings > Art style. Home has detailed painted 2D ships with distinct silhouettes: hooked drones, needle couriers, heavy gunships, armored elites and three different mini-bosses. The five player hull shapes retain their identities and equipped weapon readouts. Light craft weave within bounded paths; selecting a target holds that extra movement while you read. The planner still controls descent, word counts and deadlines.
Multi-word hulls and bosses have intact, damaged, heavily damaged and critical images on exactly the same canvas and pivot. Completed words progressively reveal breached armor, broken weapon housings and hot internal machinery. Rewind restores the appropriate image. Boss damage persists across phases; shield projectors show broken emitters and cracked shield faces. Word cells remain separate readable indicators, with fewer visual cells on small craft.
Exhaust starts at engine nozzles; attack flashes and shots start at weapon mounts. Sparks and sampled hull fragments emerge from damaged components, with size and outward speed scaled to the craft. Boss destruction lasts about 2.4 seconds: component blasts, a core rupture, then separating wreckage. Combat stops and rewards are banked immediately; the reward card appears afterward. An explicit pause or hidden tab also pauses the destruction sequence.
Words, targeting cues and attack warnings remain crisp overlays. Reduced motion retains damage images and uses a short wreck fade without moving debris. Ink, Rendered, Pixel and Vector remain selectable. Painted Fleet uses Ink outside Home and the existing fallback if a sprite fails to load.
The Home fleet preview, linked in this manual's footer, lets you inspect ship sizes, switch damage stages and replay boss destruction.
A splitter's children do not split, and neither splits when killed by a blast (a nuke or an emergency flare), because a panic button that seeded two fresh drones would be worse than useless against exactly the wave it exists for.
Incoming attacks. A hostile reaching BASE_Y launches a visible shot at the ship. The shot takes 0.65 seconds of active simulation time to arrive; damage is applied on impact, scaled by sector toughness and the hull's damage multiplier, with shields absorbing it first. The playfield has no horizontal boundary line. Impact sparks and a short hull-hit or shield-absorption message explain the result. Pending shots hold the wave open and freeze during pauses and protected cards. A boss launches at its existing attack range, and defeating it before impact cancels its shot. A successful full boss clear also neutralises remaining escort shots before the reward review. A boss shot that lands ends the attempt even if a shield absorbs the damage. Power-up drops never attack.
7.4 Targeting and the lock#
| Rule | Behaviour |
|---|---|
| Acquiring | The first character locks the LOWEST threat whose word starts with it and whose typing has not begun. Lowest is both the most urgent and the one your eye is already on |
| While locked | Every later keystroke belongs to that lock, for as long as there is anything typed into it |
| A lock at zero characters | Commits you to NOTHING, except for a held phrase still waiting for its separator. A stack can hold its lock across a word boundary. Its own next letter is answered first, which reads a sentence straight through; once the separator is served, any OTHER letter on the field takes you there, exactly as it would with nothing locked |
| Committing reveals | A hidden word (Blackout, the Oracle) shows itself the moment you lock it |
| A wrong key | LANDS, in red, in the box over your base. Nothing else is accepted until backspace has taken it out. It costs heat, accuracy and the combo, stalls the turret, and marks the target as fumbled for the rest of this engagement |
| The correction cap | At most three uncorrected characters (BAD_CAP). Past that the keyboard accepts nothing but backspace, so a moment of confusion can never become twenty red letters to pick out one at a time |
| A stray key | A character no craft on screen answers to is a miss (heat, accuracy, the beat) but never a correction: nothing has been committed to, so there is no word it belongs to |
Backspace | Takes back the red first, then the letters you got right, and then the COMMITMENT: when there is nothing typed left the lock is released and the whole field is open again. It never does nothing, which is what stops a target you no longer want from becoming a trap. The box carries the same control as a button, because a phone's soft keyboard reports no Backspace at all |
| Assists | Absorbs heat and firing delay for a limited number of slips per wave. The error must still be backspaced; accuracy and combo penalties remain |
| Correction at every difficulty | Keeps the correct prefix and the target. Backspace removes the typo, then typing continues from the same letter. The mistake still costs heat, accuracy, the combo and the stall. Weapons never restart the word |
Escape | Pauses during a word, pending separator or error; releases an empty target preference after Space |
| The word running out | If the meter never caught the word up, the target takes a FRESH SHORT word and starts clean |
| A word cleared | The lock is RELEASED. A craft with words left keeps flying, one word lighter and visibly smaller, and committing to its next word is a fresh decision |
Mandatory correction and stable reading text#
An incorrect character never replaces or inserts a glyph in the target word or boss passage. It highlights the expected character cells instead. A separate input line shows the accepted prefix, the actual erroneous characters and their count. The error buffer holds up to three characters; further input pauses until Backspace removes them. Correct letters do not advance while any error remains. Boss commands follow the same rule.
Space, arrow and touch discharge, offensive flares, weapon swaps and reserve nukes are blocked while an error remains, without spending resources. A departing target, help card or pause does not erase errors. Defensive reserve powers remain usable. Assists cushion heat and firing delay; no equipped item bypasses correction. After correcting a boss typo, the next ten newly scored correct characters use after-typo damage, and the eleventh restores clean damage. Backspacing previously scored text cannot farm damage, combo, recovery or escort fire.
No two craft on screen offer the same opening letter. The first letter is the whole targeting interface, so an ambiguous one would turn "commit to that target" into a coin toss the player cannot see. freeLetters() is what is taken and wordAvoiding() draws around it, at every point a word can appear: on spawn, when a craft moves to its next word, and when a fumbled word is re-issued. Both renderers draw that letter in the accent colour. The picker always makes the same NUMBER of draws from the seeded stream whatever it chooses, which is what keeps a Versus room in step while letting two clients with different screens resolve a collision differently.
Corrections always preserve correct letters. The old Strict setting is retired, including on existing profiles. Ace and Nightmare increase combat pressure without changing how Backspace works.
Each owned weapon or attachment occupies one slot. Fitting an item from the other mount moves it and clears the source attachment slot (weapons fall back to the starter or an empty secondary). Old duplicate loadouts keep the primary copy and clear the secondary without deleting ownership or upgrades.
7.5 The capacitor, and the accuracy axis#
Typing a word out destroys it. Nothing below changes that. What follows is what the typing EARNS alongside: every correct character also banks CHARGE, measured in letters, through chargeFor():
c = clean ? dmgClean : dmgFoul
c = c * profile.clean or profile.foul (the encounter's vulnerability, if any)
c = c * (1 + min(0.6, comboDmg * combo)) (comboDmg weapons only)
c = c * pierce * profile.pierce (inside a boss transmission only)
c = c * swapDamageMult(msSinceSwap) (0.5 rising to 1 over 600 ms)
c = c * chargeRate (the meter's own speed)
floor at 0.05, capped in total at chargeCap
chargeRate is 0.4 at the baseline, and that number is the whole balance of the feature: a correct character banks a little under half a letter, so about two and a half words of typing buy one word deleted for free. At one letter a character every word typed would buy another outright and the field would clear twice over.
clean is whether the CURRENT WORD has been fumbled, not whether the run has. The bargain is per engagement: a slip costs you the charge those characters did not bank and nothing else, which keeps it a cost rather than a spiral.
A discharge costs the target word its remaining untyped letters, less anything a neighbouring kill's splash has already paid into it. Up Arrow or Fire spends that much and KEEPS the rest, so surplus carries. With nothing locked it fires at whatever is closest to your base, which is the only auto-target that is never wrong.
What this feels like. With a Lance (1.55 clean, 0.45 foul) ten clean characters bank a free six-letter word; the same ten characters after a slip bank about a fifth of one. The word you were typing dies either way. So accuracy does not save you keystrokes on the word in front of you, it hands you whole words you never have to type at all, and the letters a discharge skips are a SNAP SHOT worth 14 each. Nothing is deducted and nothing scolds you: you simply earn less of the one thing that saves you time.
AREA weapons (blast above zero) are the other shape a capacitor can take. They fill slower. A discharge spends exactly blastCost charge and clears one current word on every threat inside the radius, regardless of word length. Surplus charge stays banked. The bargain is not precision against forgiveness but patience against reach: hold through a thin wave, dump into a thick one.
A decision about WHEN, made blind, is a guess, so both renderers say what a discharge would take, and they say it ON THE CRAFT rather than as a circle over the field. Every threat standing in the blast is MARKED (corner brackets in the 2D view, its own lock ring in the 3D one), green once the meter could pay for the shell and violet while it could not. The radius itself is never drawn: what the player is deciding is which of these die, and that is a property of each craft rather than a shape in the sky. The marked set is computed once a frame from the same target and radius the discharge uses, so it can never promise a set different from the one that dies.
A mistype STALLS the turret for the weapon's stall (240 ms base, 60 to 600 after gear). The stall stops FIRING and not typing: a slip already costs accuracy, the combo, heat and a backspace, and taking the keyboard away as well would be four punishments for one mistake and would read as the game freezing. Correcting the mistake ends the stall early, because by the time a player has found the key the beat has been served.
accuracyDemand() collapses all of that into one sentence, and it is what the Armory, the pre-flight briefing and the in-game weapon chip all show: the accuracy at which this loadout's charge-per-second crosses the honest baseline's. It compares the two at every accuracy from 70% to 100%, weighting the clean and fouled numbers by the odds of getting a five-letter word out without a slip, rating a combo weapon at the streak that accuracy actually earns, and charging for the stall. A precision weapon reads "pays off above 88% accuracy", a forgiving one "pays off below 95%", an area weapon "pays off on a crowd, not on accuracy". It is derived rather than written beside an item, so it cannot drift from the numbers it describes.
Splash pays splash * targetWordLength into the nearest other threat's current word, within a squared distance of 0.35 (with x scaled by 1.4 for aspect), making the next deliberate discharge over there cheaper. Credit is capped at the current word's length. Splash never advances the locked target or a word already partly typed, including a held phrase at its separator. Unengaged targets can still be destroyed by collateral. Ordinary typing always completes every visible letter. A fully credited word can be discharged for zero charge with a single-target weapon. Area weapons retain their fixed shell cost. The arc shows where the credit landed. Splash cannot chain-trigger power-ups and does not reach the encounter's own craft.
Boss phase command words appear in a large card centered in the playfield. After the command is typed, the paragraph returns to the bottom reading panel. Character positions remain stable while typing in both surfaces. The space cursor uses the original plain cyan block, without an extra open-box symbol. Boss review freezes gameplay, but cosmetic particles, flashes, shake and scrap numbers finish fading on a separate presentation clock. Obsolete auto-shots are removed when the full boss encounter is cleared.
Weapon controls and power-up reserve#
The item picker and Hangar keep a sticky wallet visible while browsing. It shows current scrap and boss cores and refreshes after purchases or upgrades, including when the catalogue is scrolled on a phone.
| Action | How to trigger it | Cost and result |
|---|---|---|
| Type a word | Type the visible letters | Clears that word and earns charge for correct keys |
| Fire an extra shot | Up Arrow or the Fire button; Space outside a phrase separator | Single target: remaining letters minus splash credit. Area: fixed blastCost. Surplus charge stays stored |
| Switch weapon | Tab with a second mount fitted | Only the active weapon and its attachment contribute; brief spin-up reduces output |
| Collect asteroid loot | Destroy the asteroid; the drop collects automatically after its animation | First collection stores it, then offers guided practice. No pickup typing or function key is required |
| Use stored power-ups | F1 Shield, F2 Slow, F3 Repair, F4 Focus, F5 Nuke, or click a slot | One charge per type; consumes it only if it can have an effect |
| Review power-up rules | How to use, above the five slots | Pauses solo play for the full explanation |
| Read weapon rules | Loadout weapon card; Compare on a picker or vendor card | Actual resolved build, including upgrades, mods and gambits; no equipment changes during comparison |
The loadout and vendor descriptions share js/weapon-guide.js. Their clean and after-typo rates come from chargeFor, boss output from damageFor, and streak thresholds from the resolved comboDmg. A six-letter single-target shot is the common comparison; area weapons state their fixed shell price. These examples start from empty charge and zero combo with neutral target armour. They compare steady rates, not a prediction that every future word will remain fouled. Normal words reset on the next word. Bosses and mini-bosses recover clean damage after correcting the error and typing 10 consecutive new correct keys, or a new passage, whichever comes first. Further typos restart the count without stacking a deeper penalty. Backspace does not reset recovery and already-scored keys cannot advance it.
Higher charge earnings buy extra shots sooner. Precision weapons trade a strong clean rate for a weaker rate after mistakes; area weapons trade slower charging for several targets per shot; streak weapons reward sustained accuracy. There is no universal best weapon. Home's pacing remains solvable with the starter single-target weapon and without reserve power-ups.
A mini-boss clear restores waveActive; clearing a mini removes only its own escorts and keeps the ordinary wave queue. A full boss clear first settles its idempotent receipt, then removes enemies and enters bossVictory before rendering the review. A settlement exception cannot remove the boss and leave an empty active encounter. Boss combat contributes to active combat time.
7.6 Heat and jams#
| Constant | Value |
|---|---|
| Heat per mistype | 13, times any event heat multiplier |
| Heat capacity | 100 base, heatCap from gear (clamped 50 to 320) |
| Heat decay | 20 per second base, heatDecay from gear (clamped 8 to 80) |
| Jam duration | 1,150 ms |
| Heat left after a jam | 45% of capacity |
| Wave start | Heat resets to 0, or to 50% under the Hot Start gambit |
A jam preserves the lock, the correct prefix and the correction buffer. It shakes the screen and pauses typing and firing; Backspace still corrects during the jam. It deliberately does not count the ignored keys as mistakes: the mistake that caused the jam has already been paid for, and charging again for the second you cannot fire would make heat a spiral rather than a lesson.
7.7 Combo, score and the Zone#
Combo is consecutive correct characters. A mistype applies the equipped combo retention. Combo still drives feedback and streak-sensitive combat equipment. It does not multiply current Starfall score or XP.
The compact combat HUD keeps the active build's Charge bonus visible below the weapon, with a meter toward +60% and the combo needed to reach it. A typo briefly shows the actual streak change, including any retained bonus. A build without this stat says None. During exposed-core typing the label changes to Boss damage: its percentage compares damage per new correct key with and without combo, after the boss damage cap. If the cap limits the benefit, the panel says Damage capped and states the raw streak separately. Shield commands and repairs label the bonus Streak ready instead of claiming to deal damage. Armory explains the per-combo gain, typo retention and cap using the active build's resolved equipment.
Score is the sum of defeated targets' performance receipts, defined in section 4.1. A word, kill, snap shot, boss, phase or wave does not grant additional flat points. Skipped text cannot score. Faster accurate work earns more points for the same original target; an unrewarded echo or reset cannot be farmed.
The Zone remains a feedback and contract-progress state. It lights after a combo of 20 with at least 96% accuracy over the recent 40 keys (at least 12 samples), tolerating one miss. Its time is recorded for Flow State and other progress, but it adds no Starfall score or per-second XP. Training and legacy run formats retain their prior Zone reward rules.
FOCUS is a combat aid: it halves mistake heat for 10 seconds, extendable to 20 seconds. It no longer doubles score.
7.8 Asteroids, rewards and flares#
ONLY AN ASTEROID PRODUCES A TEMPORARY POWER-UP, AND NOTHING ELSE IN THE GAME DOES. An asteroid is an OPPORTUNITY: it is excluded from the wave's mandatory work, it never blocks a wave from ending, it does not raise the required pace, and it can only hurt you by physically hitting your ship.
| Field | Meaning |
|---|---|
role: 'opportunity' | Not counted as work anywhere: not in the wave budget, not in the threat meter, not in the wave-clear test |
rewardType | Chosen when the rock is PLANNED, not rolled off the kill |
collisionPolicy: 'player-hull-only' | The base line is not an attack line for it |
blocksWaveClear: false | Ignoring it can never delay progression |
| Reward | Effect | Duration | Cap |
|---|---|---|---|
NUKE | Spends stored charge with a 20% damage bonus across eligible visible enemies; keeps unused charge and protects the current reading target | instant | |
SHIELD | Restores 22% of maximum hull and adds 20 shield | instant | 60 shield |
SLOW | The whole field descends at half speed | 8,000 ms | 16,000 ms |
FOCUS | Half mistake heat | 10,000 ms | 20,000 ms |
REPAIR | Correct typing repairs hull while invulnerable; each typo removes one second | 10,000 ms from activation | 50% of maximum hull |
Destroying a rock reveals its named reward at the impact location. Typing, a deliberate weapon shot (including an area shot), or splash can earn it. Destruction and collection never activate the effect.
First discovery: the reward appears for 700 ms, then collects automatically into its empty slot. No typing, click or function key is needed. Its 600 ms collection flight finishes before solo play pauses for a guided game scene. The pickup never takes the current typing target. Collection stores the item; only later activation spends it. The lesson's copy is disposable, so watching it leaves the real pickup available. Skip or Escape returns to the same game. Discovery is saved per profile and survives reloads and profile sync. Multiple discoveries queue their lessons; protected encounters delay the next lesson.
Five fixed slots: F1 Shield, F2 Slow, F3 Repair, F4 Focus, F5 Nuke. Each type holds exactly one charge; the buttons are also clickable and touchable. Keys never shift when another slot empties. A key repeat cannot spend an automatically collected replacement. F1-F5 browser shortcuts are suppressed only while the game owns the keyboard; modified browser shortcuts and dialogs retain control. Some keyboards require Fn. The How to use button reopens the explanations.
Charged nuke: its finite total budget is stored charge * 1.2, shared across targets. It removes complete words, never a partial word. The entire locked target, partially typed targets and phrases awaiting a space are protected, as are bosses, typed-only objectives, opportunities and pickups. The slot previews estimated damage and charge spending, or explains why it cannot fire yet. Actual spending follows the words removed, so excess charge stays banked. With no affordable eligible work, both the item and charge remain stored. Nuke kills cannot chain splash or splitter spawns, and grant no false correct keystrokes or WPM. Optional power-ups remain excluded from the mission's loadout forecast.
Hull repair: F3 consumes a stored Repair, opens a typing passage and starts ten seconds of invulnerability. Correct characters restore hull, including spaces, with a repair sound on each healing key. Recovery is calibrated to 50% of maximum hull over ten seconds at the mission's reference typing pace, capped at 50% per item or full hull. Armour does not reduce healing. A full hull keeps the item stored. Each typo only subtracts one second, without combat damage penalties, heat, charge loss or a combo reset, and without advancing the cursor; typing the expected character resumes healing. F3 cancellation ends the attempt immediately.
Enemies, projectiles, spawns and boss clocks continue during repair, but incoming hits cannot damage hull or consume shield points while repair is active. A protective outline, INVULNERABLE countdown, blocked-hit ripple and sound show the protection. Reduced motion keeps a steady outline and blocked-hit label. Protection ends on completion, cancellation or timer expiry, including expiry caused by mistakes. Expiry immediately removes the passage and restores enemy targeting, even mid-word. The activation timer prevents unlimited protection without typing. Repair keys earn no weapon charge or combat damage. Their practice accuracy is recorded separately from combat productivity. Each typo is counted once. Cancellation, expiry, full hull and completed healing all return control immediately. Previously discovered EMP pickups migrate to Repair discovery.
Hull and shields have separate bars and point counts in the HUD, beside typing labels and on the boss/repair reading panel. Equipment can supply more starting shield than the 60-point pickup cap; the display preserves that capacity, and using a pickup never reduces an existing shield. Typing labels render above combat effects. Kill receipts move out of their way; notices yield visibility whenever they intersect current enemy text or the active reading/input panel.
Familiar pickups: the named drop appears for at least 700 ms, then fills its empty slot with a visible collection cue. Reduced motion uses a stationary confirmation instead of collection travel. If its slot is full, one spare drifts downward with a visible countdown and disappears after 15 seconds of active play. Use the stored item before expiry and the waiting copy collects automatically, without activating. There is one waiting drop per type; additional copies merge with that spare without resetting its timer.
Transitions: waiting drops carry their remaining lifetime across waves and boss fights. Pauses and protected review cards freeze their timer; active combat transmissions continue it. Familiar drops can fill an empty slot during combat transmissions. Stored charges have no timer, but both inventory and waiting drops end with the run. Neither drops nor stored items block wave completion, raise required WPM, receive damage or count as kills. Versus retains its shared clock: first discoveries explain themselves in a nonblocking notice instead of pausing the race.
The planned asteroid cadence is unchanged. At most two unbroken rocks are allowed on screen; full inventory no longer suppresses the opportunity schedule. Power-ups cannot generate other power-ups.
Stacking is centralised and capped. Timed effects extend up to their stated cap. A reward that would do nothing remains stored with an explanation; it is never silently replaced with a different effect. Controls are inactive while paused, on a transition or reward card, and after the run ends. During a boss, stored effects can be used, but a nuke clears escorts rather than the boss.
Cadence, not chance. In Home there is at most one opportunity in the air at once, at most two a wave, roughly one per 24 seconds of ACTIVE COMBAT TIME, and none before wave 2. Active combat time excludes every card, countdown and protected transition, so pausing can never accumulate a backlog of overdue rocks. Boss fights suppress them entirely (during a transmission the keyboard belongs to the paragraph, so a reward the player cannot choose to take is not an opportunity). After the last enemy, visible asteroids remain targetable until destroyed or until they naturally leave the field. Unspawned opportunities are discarded at wave end; they do not extend the wave. A boss introduction still sweeps loose asteroids without damage or reward. Revealed power-ups are separate and survive both transitions.
Collision means contact with the ship. Ordinary hostiles fire aimed shots when they reach attack range. A rock is not aiming at anything, so it is tested against the ship's own hull, SWEPT from its previous position to its next so a fast rock cannot tunnel through on a long frame, in simulation coordinates with the field's aspect applied. A direct contact damages the ship exactly once (10, before the sector and hull multipliers); a rock that crosses the base line anywhere else leaves harmlessly, with no damage, no miss, no combo loss and no penalty for having chosen to ignore it.
What this replaced. Every kill used to roll for a power-up: 85% from a "dropper" and 3% from everything else, and the drop spawned another typed target. That made a shield, a slow or a screen clear an ordinary consequence of playing rather than something you chose to go after, and made "can I survive this wave" a question about luck. And an asteroid used to damage the base for crossing the base line at any x position at all, which is why ignoring one felt punished.
Auto-fire never prefers a reward. The turret's shot, an untargeted discharge and an emergency flare all aim at the earliest ACTUAL damaging impact, skipping harmless pass-by rocks and skipping a target already guaranteed to die from a round in flight.
Emergency flares (Ctrl + Space) instantly destroy one threat during ordinary waves, prioritizing the enemy closest to the base. If only opportunities remain, a flare can destroy an asteroid and store its reward. Flares do not spend weapon charge and cannot be fired during boss fights.
Your module and its upgrades determine the rack capacity, capped at 8. Racks start full and refill to that capacity after every full boss victory. The victory screen shows how many were restored. Ordinary waves and mini-bosses do not refill them, and unused flares never accumulate beyond capacity. A loadout with no flares, including the Empty Racks gambit, receives none.
7.9 Difficulties and sectors#
Current solo typing workload is selected by numbered challenge tier (section 7.2a). Cadet, Pilot, Ace and Nightmare remain combat and salvage settings; neither adds a performance score or XP multiplier. The following catalogue fields are retained for training and legacy compatibility:
| Difficulty | Legacy XP and score multiplier | Legacy speed field |
|---|---|---|
| Cadet | 0.8x | 0.75x |
| Pilot | 1.0x | 1.0x |
| Ace | 1.4x | 1.3x |
| Nightmare | 2.0x | 1.7x |
Account level unlocks destinations: Home is open at level 1, The Belt at level 4 (480 lifetime XP), Deep Field at level 6 (1,150 XP), and The Rift at level 8 (2,149 XP). Operations shows each requirement and remaining XP; Armory repeats the requirements alongside equipment advice. Complete runs to bank XP. Training and Flight Check placement also count toward account level. Power remains a recommendation once a destination is open.
These are provisional milestones, estimated around successive opening thirds (roughly waves 1-17) at 40-50 WPM and 95% accuracy. They are not wave requirements: faster players earn XP sooner, slower players later, and charges reduce paid typing work. Existing saved boss clears preserve access to that sector. A stale locked destination falls back to Home when the hub or a solo run opens.
| Sector | Recommended power | Speed | Damage taken | Salvage | Cores | Boss every |
|---|---|---|---|---|---|---|
| Home Sector | 0 | 1.00x | 1.00x | 1.00x | 1.0x | 5 waves |
| The Belt | 26 | 1.14x | 1.10x | 1.25x | 1.2x | 5 waves |
| Deep Field | 58 | 1.32x | 1.25x | 1.60x | 1.5x | 5 waves |
| The Rift | 92 | 1.55x | 1.45x | 2.20x | 2.2x | 4 waves |
The sector's damage column multiplies what reaches your hull AND the health of a boss phase (a phase's health is the length of the transmission that opens it, times this number).
Skill substitutes for gear, at SKILL_POWER_PER_WPM = 1.2 points of power per WPM above the 40 WPM reference. So roughly every 10 WPM is worth 12 points of power, because a threat destroyed early never tests your hull at all. sectorAdvice() says this in words rather than blocking anybody.
7.10 The end of a run#
A run ends when the hull reaches zero, or when an optional duration expires (a timed Versus round, which counts as complete because the player played the whole thing). Leaving the tab or navigating away calls stop(), which abandons: no score, no XP, no History, no board, and only the keystrokes go to the skill model.
What Starfall reports to finishRun():
| Field | Contents |
|---|---|
game, mode | starfall, and the board scope (the difficulty, or versus) |
complete | Whether the run ended properly |
durationMs, typed, correct, wrong, wpm, netWpm, acc | The plain measurements |
keys | The per-character and per-bigram tally, with timings |
score, wave, combo, difficulty | The arcade extras |
meta.events | The encounter ledger: what fired, whether it cleared, its multipliers, characters typed under it |
meta.zoneMs | Milliseconds held in the Zone |
meta.sector, meta.lootMult, meta.gambitMult | What was flown and what was bet |
meta.medianMs | Median keystroke latency, for the worker |
The game computes NO rewards. It reports facts, and js/run.js decides what they were worth.
7.11 The renderer#
Starfall draws on ONE canvas: the hand-drawn 2D view in js/games/starfall.js with the generated sprite layer from js/art.js over it.
It used to have a second, optional 3D view (Three.js and troika-three-text, vendored under vendor/, lazily imported, reached at its own /3d address behind a settings.render3d toggle). That view is retired (owner's call, 2026-08). It was removed rather than switched off: the module, the whole vendor directory, the import map, the /3d and /2d redirects, the setting, the URL switch, the Settings toggle and the probe3d() test seam are all gone, and with them the only third-party code this repo has ever carried. It is in git history if it is ever wanted back.
Two rules outlived it, because neither was really about having two views:
- The draw reads the run and never writes to it, and above all never draws from
R.rnd. A decorative draw that consumed the simulation's randomness would desynchronise a Versus race by being pretty. Anything that needs randomness seeds its own generator offR.seed, the way the starfield does. - Nothing may assume a sprite loaded.
spriteOk()gates every image draw and the hand-drawn vector shape is the fallback, so deletingassets/art/changes the look and nothing else.
7.12 Art, and what happens when it is missing#
The flat view draws generated sprites over its hand-drawn shapes, and the Arcade and Armory are staged with rendered backdrops. js/art.js is the one module that knows where any of it lives; everything else asks by key and never hard-codes a path. ART_DIRECTION.md is the companion document that says how the art itself was made and what it has to look like.
Two families, with two different compositing contracts:
| Family | What is in it | How it is drawn |
|---|---|---|
| Holograms | Gear icons, the galaxy, the scrap and cores currencies | Glowing objects authored on pure black, composited additively (screen blend in the DOM, lighter on canvas), so they need no alpha channel and can never grow a halo |
| Solid craft | Threat sprites, bosses, mini-bosses, hull renders, the player's base | Real alpha cutouts, because they occlude the starfield rather than adding to it |
Vector fallback is law. Nothing that draws a sprite may assume it loaded: every caller checks spriteOk() and falls back to the hand-drawn shapes that shipped before any of this art existed. The app must play identically with assets/art/ deleted, the same way it already must play without webfonts and without Firebase. Every function in js/art.js is best-effort and silent, so a missing file downgrades the look and changes nothing else.
Two consequences worth knowing:
- An asteroid picks one of two rocks from its own seed, so a field of them is not one rock stamped twenty times, and the pick is a pure function of the threat rather than a fresh roll (a roll would desynchronise a race).
- The player's craft is a cosmetic, so the sprite is tinted at runtime. Five neutral base renders are recoloured from the equipped cosmetic's own hull and trim hex and cached per combination, which keeps cosmetics purely cosmetic while still letting them change what you look at.
7.13 Four art styles, and how to change them#
Settings carries an Art style row with four complete looks for the same game. It changes pictures and nothing else: no rule, no number, no reward and no leaderboard reads it, so switching mid-account costs nothing and proves nothing.
| Style | What you get |
|---|---|
| Ink | The default. Cel-shaded craft with a hard ink line, flat colour and a lit rim, rigged in moving LAYERS like the pixel pack and the only style that shows your fitted gear ON the ship. assets/art-ink/, about 1.4 MB. |
| Rendered | Pre-rendered craft, holographic gear icons, painted sector vistas. The look that shipped first. assets/art/. |
| Pixel | Hand-drawn pixel art, generated inside the repo by tools/build-pixel.mjs and composited in moving LAYERS by js/rig.js. assets/art-pixel/, about 51 KB for the whole set. |
| Vector | No sprites at all: the hand-drawn shapes underneath. The lightest of the four, and the one that proves the fallback guarantee above is real rather than theoretical. |
A layered pack's layers are the simulation, drawn. This is the part worth knowing as a player, because it means the craft are telling you things the HUD would otherwise have to:
- A craft's lit cells count the words it still owes you. Type one of a cruiser's three words out and one of its container lights goes dark, so how much typing is left is legible from across the field before you read a letter.
- A Dreadnought's four armour plates are four separate pictures, and one leaves the ship every time a phase is stripped. By the last phase you are typing at something visibly naked.
- A splitter's two pods walk apart as its health falls, so the split is a promise being kept rather than a surprise.
- A shield lattice is drawn only while the shield is really up, so the picture stops lying the instant the command word lands.
- Engines burn from how fast a craft is actually descending, which is the same number the threat meter is worrying about, so a fast wave looks fast before the meter has said so.
- A cruiser's scanner head turns to face your base. It has noticed you.
Proportion carries the rest of the report: a Warden is the widest craft in the game because it blocks, a Raider is the narrowest because it is fast, a courier is flat because it is carrying rather than fighting.
Two things are true of the Ink pack and of nothing else:
- The exhaust plume is a speedometer. Every craft's flame comes off one drawing at three lengths, picked from how fast that craft is really descending, and it is the same three lengths on every craft in the game. So the longest flame on the field is always the thing that reaches you first, and you read it where your eyes already are, which is at the words.
- Your ship wears what you fitted to it, and you can click it. The Hangar's pad and the pre-flight's craft are composited from the hull plus one overlay per mounted item: the coolant vents, the reactor, the module pod, the two attachment plates and the nose sensor, each pinned to a hardpoint the art pack itself declares, an empty socket drawn as a blank bolt plate rather than as nothing, and the picture changing the moment you equip something. On the Hangar's pad every one of those parts is also a HIT TARGET: click the vent to change the coolant, click the hull between them to change the hull, and the same list opens that the card beside it opens.
The weapons are the exception, and they are deliberately not drawn on the hull. A gun is the one part that has to be identifiable at a glance, and seen from directly above, small, and dark against a dark hull, it was not: it read as a lump on the exact part of the picture the screen exists to decide. So the two hardpoints are a pair of large slots across the base of the bay instead, split by a slash, carrying the weapon's own inventory art at a size you can recognise, its power, its position on the axis and the accuracy it pays off at. Presentation retreated where it was not earning its place and the decision got the room instead.
The sky behind the field belongs to no pack, and it moves. The playfield's backdrop is three bands of points and specks scrolling at three different rates. The nearest and fastest band is deliberately the faintest thing on the screen, because the words are typed over all of it. The scroll speeds up with how fast the field is really descending and is capped so it can never strobe under the text, and reduced motion slows the whole thing to a crawl. Which sector you are in is said by how thick the debris is and by the sector-tinted nebula behind it, never by a shape.
There used to be a fourth and furthest band carrying the sector's own architecture: a watch ring and traffic lanes over Patrol Space, rock shelves in the Belt, hulls at extreme range in the Deep Field, a torn seam and its wrecks in the Rift. It is gone (owner's call, 2026-08). Each of those was a large shape filled at half the surface token, which on a near-black stage is a visibly lighter patch with a hard edge, and the Home ring at playfield width was two hundred pixels across sitting directly behind the base. The band moved a pixel a second, which was meant to keep it out of the way and instead meant it read as a rendering artefact rather than as scenery. The rule that replaced it: a backdrop element is either small enough to read as texture or fast enough to read as motion, and large-and-nearly-still is neither.
ART_DIRECTION.md sections 8 to 12 are the full contract for the newer styles, including how to regenerate each pack, what every layer means, how the sky is built and how gear is socketed onto a hull.
The weapon you are firing is visible. weaponVisual() derives the turret's SILHOUETTE from what the weapon actually does rather than from its id: a splash weapon flares, a combo feeder carries its drum, a precision weapon lances. What it FIRES comes from the identity table instead, because a projectile has to be nameable from the corner of the eye rather than merely appropriate (section 7.15). Both are recomputed on every swap, so pressing Tab visibly changes the gun before a single shot is fired.
7.13 Music#
Four tracks, played by scene and crossfaded over 900 ms so a scene change is a dissolve rather than a cut:
| Scene | When |
|---|---|
ops | The map room and every calm deck: the arcade hub, Training, Progress |
hangar | The Armory |
combat | A live Starfall run |
boss | A boss or mini-boss encounter, back to combat when it resolves |
Music is on by default at half volume, and both are in Settings. js/music.js creates no element and fetches no byte until music is enabled AND a scene is set AND the browser has seen a user gesture, so a visitor who never plays never pays for it. Every play() is best-effort: an autoplay refusal parks the intent and the first real gesture retries it, and nothing in the module throws, because a music failure must never cost a frame or a keystroke.
This is the one deliberate exception to "no audio assets". Keystroke-rate SOUND EFFECTS stay synthesized in js/audio.js, because they fire from keydown handlers and must never wait on a decode. Music is long-form and streamed, so files are the right tool for it and the wrong tool for a keypress.
7.13b What your build sounds like#
The sound describes the WEAPON THAT IS EQUIPPED, and the audio layer computes nothing. The keystroke blip used to climb a pentatonic scale off the RAW COMBO and, past fifteen characters, cycle inside the top octave forever. The gameplay underneath it is nothing like that shape:
| The gameplay | What the old sound said |
|---|---|
The streak bonus is min(0.6, comboDmg * combo), and comboDmg is a RESOLVED stat: 0 on most weapons, 0.009 on a Chain Driver, 0.005 from a Tracer Feed, moved again by upgrades | The same combo of 40 sounded identical on all three, though it is +36%, +20% and +0% |
| The bonus CAPS, and past the cap a longer streak changes nothing | The blip kept climbing, which sounds like more power arriving |
comboKeep RETAINS a fraction through a mistake: combo 40 can become 20 and +56% become +28% | It fell all the way to the bottom, which is what a total loss sounds like |
| Most weapons have no streak stat at all | It played the whole ascending melody for a bonus that does not exist |
The fix is not a cleverer mapping in the audio file: that would be a second copy of the equipment rules, and a second copy drifts. The simulation resolves ONE result per action, js/games/combat-feedback.js projects it into semantic facts from the SAME resolved stats the damage used, and js/audio.js renders those facts.
A musical rise with real state. The main short pluck rises from 220 to 880 Hz along a smoothly interpolated pentatonic contour as stored charge goes from empty to full. It holds at capacity and drops after charge is spent; a mistake does not empty the capacitor. A full cue sounds once on entering full, including weapons with no streak bonus. A quiet lower voice tracks the actual streak bonus independently. During boss transmissions the main note follows the streak bonus, since boss typing deals damage directly and does not bank charge. Sine and triangle voices keep this soft at fast typing speeds. Loadout offers separate charge and streak listening previews, using the same sound mapping.
Absolute bonus landmarks. The streak voice maps to the real bonus percentage at +0, +10, +20, +30, +40, +50 and +60, interpolated between, so the same +30% sounds like +30% on every build. Timbre identifies the weapon; it does not redefine what a percentage sounds like. Cap completion is a separate fact (atCap, capFraction), so a future weapon with a different ceiling cannot make "half of this weapon's maximum" and "+30%" the same sound. The bonus itself stays continuous: +20.7% is not rounded to +20% in combat to match a sound.
| Loadout (base level, nothing else contributing) | Coefficient | Combo reaching +60% |
|---|---|---|
| Service Repeater, no streak mod | 0 | never: no streak bonus |
| Chain Driver alone | 0.012 | 50 |
| Service Repeater plus Tracer Feed | 0.007 | 86 |
| Chain Driver plus Tracer Feed | 0.019 | 32 |
Those thresholds are DERIVED from resolved stats, never from item names: upgrades, attachments and gambits all move them.
Three facts, three cues. "Streak +30%", "this word is fouled" and "the capacitor is full" are three different mechanics, and the old single rising tone collapsed all of them into one. A mistake emits ONE composite consequence after every rule has applied: the error cue, then a transition DOWN to the bonus the player really has (ending at the retained value, not at zero), plus a separate restrained colour if the current word lost its clean output. A mistake that leaves the bonus still at its cap plays no streak-collapse sound at all. Keys pressed into an already blocked correction buffer get the plain error cue and never announce a loss a second time. A shield command reports a command action, never fictitious damage or charge. Retyping a boss character that already landed is ACCEPTED INPUT and not new output, and is reported as such.
And the HUD says the same thing. The strip carries the SCORE multiplier and the WEAPON bonus as two separate readouts, because they are two different mechanics and a x3 score chip was being read as damage. The combo tooltip no longer says "correct characters in a row", which combo protection made simply false.
7.14 The threat meter: Required WPM#
js/games/threat.js. One number, and it ships with its own sentence:
Threat, Required WPM: the sustained typing speed, at perfect accuracy, that would clear every WINNABLE threat before it deals damage, typing in deadline order.
It is not a mood ring. It is a schedulability bound, and it is worth knowing where it comes from, because the shape of the maths is what makes the number trustworthy.
The grounding. Every threat on the field is a job with two numbers: the characters still to be typed to neutralise it, and the seconds until it deals damage. The player is one processor. A set of jobs is clearable at a speed s exactly when, for every deadline, the characters due by then fit inside it (the processor-demand criterion for earliest-deadline-first scheduling on one machine). So the minimum feasible speed is the worst prefix:
sort jobs by deadline
demand = max over k of ( sum of chars due by deadline k ) / deadline k
Required WPM = demand * 12 (one word is five characters)
That is honest and it is also unusable raw, for two reasons, both of which get a surgical fix rather than a fudge factor.
Fix A, the deadline floor. As any single enemy's arrival approaches zero, its term approaches infinity, and a HUD number that runs away at the exact moment the player most needs to read it is worse than no number. Every deadline is therefore floored at FLOOR_S, 2.5 seconds, which is about perceive plus retarget plus type a short word: the granularity a human can act at. Every term is then bounded by the total outstanding characters over 2.5 seconds, so no denominator can reach zero and a closing threat SATURATES instead of diverging.
The floor's other half, which is not optional. Inside 2.5 seconds the number systematically understates: twenty-five characters due in two seconds reads as 120 WPM and truly needs 150. The number is a LOAD AVERAGE; the sub-floor urgency is carried per threat by the word plate itself (section 7.15). Ship one without the other and the metric lies exactly where it matters.
Fix B, triage. A threat no human could still save is not demand, it is history, and leaving it in the sum makes the number scream about work that cannot be done. So a threat leaves the sum when its characters exceed what 200 WPM could produce in the time it has left, and rejoins when it drops back under 85% of that line. The gap is hysteresis, so the boundary cannot flicker, and the exit is reachable in principle whenever a deadline GROWS (a slow field, a stall mechanic), which is why LOST is a state and not a tombstone. Watching the number collapse from 160 to the winnable residue IS the triage signal.
The knowable future. The spawn queue and its cadence are state, not a die roll, so spawns due inside the next six seconds join the sum as long-deadline jobs at their expected character count. Their deadlines are far away, so the maths discounts them naturally: a heavy wave three seconds out reads as a calm pre-warning rather than a spike, and nothing peeks at the random stream to produce it.
Weapon independence is a hard constraint. Characters are counted at the honest baseline: one correct character erases one letter, which is exactly what the starter weapon does. Splash already paid into a word, weapon bonus damage and the capacitor never enter. The number is identical whatever is equipped, which is what the owner asked for and what makes it comparable between two players and between two of your own runs.
And demand and supply are the SAME UNIT. Both sides are paragraph-equivalent characters, through js/games/typing-effort.js: the demand is what the field still asks for, and the capacity is measured from the work the player actually got through, not from the raw characters they pressed. Comparing a weighted demand against an unweighted supply is exactly the failure the effort model exists to stop, and it is why there are now two labelled WPM figures on screen (REQ at the top of the field, "Your WPM" in the strip) rather than two unlabelled ones that appear to contradict each other.
| Term | Counted as |
|---|---|
| The word being typed | Its untyped letters, divided by that content's rate |
| The words behind it on the same craft | Their full length, plus the separators the craft will ask for |
| An acquisition | Once per ENGAGEMENT, and NOT at all for a target already held. A live readout that kept charging the same third of a second every frame was most of why Required WPM drifted upward while the player was typing |
| A splitter | Plus its children's OWN pre-costed work, from the plan's descriptors, rather than a flat eight characters nobody costed |
| A boss or mini-boss | The characters actually left in front of the player, capped by the plate, priced as paragraph prose; plus the transmissions behind it |
| A boss's escorts | NOT added as manual typing jobs. During a transmission the keyboard belongs to the paragraph and the escorts die to the auto-fire that same typing drives, so counting their words would state a pace nobody could be asked to type |
| An opportunity | Not a threat. Excluded, unless it is genuinely on course for the hull, which is real incoming damage |
| Mistypes | Never padded in. Errors are a capacity-side cost, and counting them on both sides would be double counting |
Smoothing. The raw bound is stepwise (a kill removes a job outright), so the displayed value is an asymmetric exponential average: it rises fast (tauUpS 0.35 s, because danger arrives quickly) and falls slowly (tauDownS 0.9 s, which is the exhale after a cleared wave). It runs on sim time, so a pause causes no jump at all.
Pressure and the bands. The number stays ABSOLUTE, because "the field wants 90 WPM" means the same thing to everybody. The severity bands are RELATIVE: demand divided by a rolling measurement of what this player actually produces under load. That measurement is LOAD GATED, advancing only while there is something to type, because a twenty-five second intermission would otherwise decay it to the floor and make the next trivial wave read as an emergency. Sandbagging is not exploitable: typing slower lowers capacity and makes the bands more alarming, not less.
| Band | Pressure | What it means |
|---|---|---|
| 0 CALM | under 0.35 | Coast |
| 1 ACTIVE | 0.35 to 0.7 | Normal play |
| 2 STRAINED | 0.7 to 1.0 | Feasible if you type cleanly |
| 3 CRITICAL | 1.0 to 1.4 | You cannot clear everything. Triage now |
| 4 OVERRUN | over 1.4 | Losing ground |
A band escalates only after the pressure has held over the line for a quarter of a second, and de-escalates only after it has sat safely under it for over a second, so the readout can never strobe between two bands at a boundary.
Bosses use attack intentions. During an encounter the required-WPM meter is hidden. The boss strip shows its next volley and time until launch, while hull and shields show how much damage the player can withstand. Escort interception shares the passage's typing, so escort words are not added as manual work.
Where it runs. As a derived pass inside the simulation, every sixth fixed step (10 Hz, so its dt is a deterministic 0.1 s), reading sim state and writing one output block. It is FEED-FORWARD: no gameplay code reads it back, it draws from no random stream, and it cannot change what spawns or how fast anything falls. A seeded replay is byte-identical whether the meter runs or not. Display quantisation (round to 1 under 120 WPM, to 5 above it, hold unless the value moved by 2 or settled for 400 ms) happens in the view, so the shown integer never flickers under the reader's eyes and the smoothing itself stays untouched.
| Parameter | Value |
|---|---|
floorS | 2.5 s |
capCps | 16.67 characters per second (200 WPM) |
lostEnter / lostExit | 1.10 / 0.85 of that line |
spawnHorizonS | 6 s |
tauUpS / tauDownS | 0.35 s / 0.9 s |
capacityTauS | 20 s, load gated |
capacityClampWPM | 15 to 200 |
capacityWarmupChars | 50, before which the difficulty's reference pace stands in |
| tier reference WPM | Cadet 30, Pilot 50, Ace 75, Nightmare 90 |
bandsUp | 0.35, 0.7, 1.0, 1.4 |
| band dwell up / down | 0.25 s / 1.2 s, de-escalating at 0.92 of the boundary |
metricEveryTicks | 6 (10 Hz) |
displayCapWpm | 240, shown as 240+ |
| Versus capacity | fixed at 60 WPM, so the same board shows the same band on every client |
What it does under each situation is pinned by tools/check-modules.mjs, because the whole value of the number is that it behaves predictably:
| Situation | Behaviour |
|---|---|
| Empty screen | 0, CALM |
| A 40-character wave arriving in three seconds | A gentle pre-warning around 30 WPM. Never a spike |
| A 25-second intermission | Capacity frozen, band unchanged across the lull |
| One tank, 20 chars, arriving in 30 s then 3 s then 1 s | Rises, then SATURATES at the floor. Never diverges |
| The same tank at 0.4 s | Enters LOST, leaves the sum, the number glides down |
| That tank knocked back to 4 s | Exits LOST, rejoins the sum, its plate restores |
| Eight five-letter craft all arriving at once | Around 160 WPM, falling as the hopeless ones drop out |
| A hovering boss with no escorts | Main 0, boss readout live. Correct |
| A mistype storm | Demand unchanged, capacity falls, pressure rises. Honest |
| Paused | Frozen exactly, with no jump on resume |
7.15 Reading the field without looking at it#
The design law behind every readout added to the arcade, and the one to argue with before adding another:
The player's fovea belongs to the word they are typing. Design as if they never look away during a wave. Anything that requires reading text elsewhere on screen mid-fight is a design failure.
That leaves three channels, and every critical state has to arrive through at least one of them:
| Channel | What it is good at |
|---|---|
| Peripheral vision | Motion, luminance, position and extent. Hue poorly, text not at all |
| The word and its caret | The one place the eyes already are: glow colour, the typed prefix, the plate itself |
| Audio | Timbre, pitch, intensity, with no visual cost whatsoever |
Four rules follow from it, and they are why the HUD looks the way it does.
Identity rides orthogonal channels. Any two things that must be told apart differ in at least three of shape, thickness, colour pair, motion, particles, impact and sound, and at least one differentiator has to survive grayscale. That makes the whole scheme colourblind-safe by construction rather than by exception.
Changes announce, states confirm. A steady glow is adapted out within seconds, so a transition gets one short salient event (a pulse, a chime) and the steady state exists to be verified when the player has a moment. The precise numbers exist for confirmation, never for discovery.
Each system owns a region and a motion. So that the worst case (full charge, OVERRUN threat, low hull and a boss all at once) cannot collide in one channel:
| System | Region | Motion | Sound |
|---|---|---|---|
| Threat | Top and side edges, pill top centre | Breathing in place, bounded | Two-note chimes |
| Charge | The base, the bottom edge, the bar under the word | Filling, then travelling ALONG the edge | The discharge, and the meter's own tones |
| Damage and hull | Full-frame flash, the base itself | A single short flash | Thud |
| Boss | The top strip, its own bar | Its own health bar draining | The boss sting and the heavier score |
| Weapons | The fire lane, the turret, the caret | Per-weapon tracer signature | Per-weapon voice |
Severity is intensity and rate, not just hue. A band change moves colour AND luminance AND thickness AND animation rate together, so it reads in far periphery and in grayscale. And motion is bounded: the vignette pulses on the change, breathes for about three seconds to announce itself, then holds STATIC. A standing strobe under maximum reading load is attention tax, and nothing anywhere modulates large-area luminance faster than a couple of hertz.
Reduced motion never means reduced information. Every cue has a static equivalent that says the same thing, listed with the cue.
| Cue | Full | Reduced motion |
|---|---|---|
| Threat vignette | Pulse, breathe, hold | Straight to the static hold at the band's opacity and thickness |
| Threat pill | Scale pop on a band change | The number, the pips and the border colour, unanimated |
| Deadline tiers | Plate outline pulsing at 1 to 2 Hz | The static underline thicknesses and outline colours, which carry the same three tiers |
| Charge strip | Travelling highlight when it can pay | The strip at its fill, brighter when ready |
| Weapon tracers | Wobble, jitter, shimmer, embers | Shape, colour, thickness and trajectory, all kept, everything else dropped |
| Resume countdown | Digits scaling in | The digits, plainly. The countdown itself is functional preparation, not decoration |
The site's Motion setting now drives all of it: the arcade used to read the system preference only, so choosing Reduced in Settings quietly did nothing here. It resolves to either.
The per-word layer. This is where the threat number's two delegations land, on the exact plate they belong to:
| Condition | Treatment |
|---|---|
| Arriving inside 4 s | A 2 px amber underline |
| Inside 2.5 s (the metric's floor) | A 3 px underline, an amber plate outline, a slow pulse |
| Inside 1.5 s | A 4 px underline, a red outline, a faster pulse |
| LOST (the triage verdict) | The plate desaturates, a diagonal strike draws across the word, and its opening letter stops being highlighted |
A struck-through word can still be typed at: the strike is a view-layer verdict and the simulation is untouched, so a splash or a flare can still take it, and the player who insists is not blocked. It is advice with teeth, in the same voice sectors use about power.
Weapon identity. Every weapon fires a visibly different projectile, so a player whose eyes never leave the word can name what they are holding from the edge of vision. Eight shape families cover the catalogue, each with its own motion signature:
| Family | Shape | Motion | Weapons |
|---|---|---|---|
| Bolt | Short capsule, halo under core | Fast, dead straight | Service Repeater, Arc Welder (slower, thicker) |
| Mortar | Fat teardrop with an ember head | Lobbed parabola, dropping embers | Flak Battery, Burst Cannon (smaller, quicker) |
| Arc | Jagged polyline in a sheath | Hitscan, geometry re-rolled every 33 ms | Chain Driver |
| Beam | Razor: white core, sheath, haze | Instant, then fades | Railgun (shimmer sweep), Lance (thin, ice-white), Longbarrel Cannon (short, amber) |
| Fan | Five dart slivers converging | Fast, each dart wavering on its own phase | Scatter Array |
| Slug | One massive round behind compression arcs | Slow, grinding, with a smoke wake | Bulwark Repeater |
| Glyph | The typed letters themselves | Steady, weaving, legible only early in flight | Nova Lance |
| Wave | A sine rope with lit crests | The phase SCROLLS along the rope | Tempest Array |
Weapons sharing a family differ in at least two other channels. Identity also reaches the fovea and the base: the turret's muzzle and rim light burn in the equipped weapon's halo and crossfade over 300 ms on Tab, the typing box wears the same pair, a kill draws a short ring in that colour AT the word that died, and the pause screen lists both hardpoints with a swatch and a one-line trait. WEAPON_IDENTITY.md carries the full recipes, including the flourishes no shipped weapon uses yet, so a future weapon lands inside the system rather than beside it.
A tracer is decoration and obeys the renderer rule: it reads the run state, never writes to it, and every decorative roll comes from Math.random and the wall clock rather than from the run's seeded streams. The hit was resolved by the simulation before the tracer was born.
7.16 Pause#
Pausing costs nothing, changes nothing that is scored, and is available at any moment of a solo run.
It is the clock, not a flag forest. The run has its own now(), and while the run is paused that function returns one frozen instant. Every timer in the game is expressed against it: the director's encounter clocks, the mistype stall, the jam, the next spawn, the breather, the elapsed time the WPM denominator is built from. So they all stop together and all resume together, with no jump and nothing to individually adjust, and no timestamp anywhere in the simulation is ever rewritten. It also means the pace a run is scored on cannot be diluted by pausing, which is the property that lets pause exist at all on a site that ranks people by speed.
| Trigger | Behaviour |
|---|---|
Escape with nothing locked, or the HUD pause key | Pauses |
| The tab going hidden | Pauses. Before this, the wall-clock timers kept running while the frame loop slept, so a player who answered a message came back to a field that had happened without them |
| The keyboard catcher losing focus mid-wave | One silent refocus attempt, then a pause. A game silently eating keystrokes reads as frozen |
| A modal dialog opening over a live wave | Pauses |
| A Versus round | Cannot pause. The key is refused and the button is not drawn, because a race against a shared clock cannot stop for one player |
Keystrokes during a pause are dropped, not buffered. A buffered key would fire into a field the player did not watch evolve. Nothing is lost by dropping: the typed prefix of the current word is simulation state and is still there on resume.
Resume is a countdown, 3-2-1 over 1.8 seconds, and the simulation unfreezes at its end. Escape during the countdown backs out to the pause screen. Two details are there for phones and for fairness: the keyboard catcher is refocused inside the resume gesture itself (a mobile browser raises its keyboard only from a user gesture, so it comes up DURING the countdown rather than after the field is already moving), and for a moment after GO, any keystroke that was already in flight is ignored, so the key that resumed the run cannot also fire into it.
The pause screen dims and desaturates the frozen field behind a glass panel carrying the run so far in sim time, the peak threat reached, the band-coloured threat history, and the loadout legend, which doubles as the place to actually learn what each weapon's tracer looks like.
7.17 The flight recorder#
Every run records itself, always, into js/games/eventlog.js. Three consumers: the F8 timeline, the pause screen's sparkline, and the headless checker, which uses it as a test oracle to assert that the game did what the screen claims it did.
The hot path allocates nothing. A record is five typed-array stores and an increment: no objects, no strings, no formatting. Strings never enter the buffer at all, so a word travels as a character code and a length, an entity as its numeric id. That is what lets the log stay on during a 140 WPM run whether or not anybody ever opens it, and formatting happens only in the overlay or on export, both of which are allowed to be slow.
Two rings, split by provenance. Simulation truth (spawns, keys, kills, damage, metric transitions) and view-side happenings (pause, resume) wrap independently, so the sim record of two seeded runs stays comparable even when one player paused fifty times and the other never did.
Ticks, not wall clock. Every record is stamped with the run's fixed-step tick counter, which freezes with the pause, so a record's place on the timeline is a fact about the run rather than about the evening it was played on. A pause therefore has no WIDTH on the timeline and is drawn as a hatched marker carrying its real-world duration, which is the honest picture of "the run did not experience this".
Alongside it, a 1 Hz aggregate (kills, misses, damage, the raw Required WPM and the band it was in) is what the sparklines draw from. The band is recorded per sample deliberately: bands are pressure-relative, so they cannot be recovered from the WPM series afterwards.
The timeline (F8, live or paused, and it costs nothing until first opened) draws six lanes on one canvas, in a colourblind-safe palette where lane POSITION always co-signals with colour:
| Lane | Carries |
|---|---|
| FLOW | Match, waves, boss start and end, power-ups, pause and resume |
| THREATS | Spawns, and the LOST verdicts |
| TYPING | One progress bar per word from first keystroke to completion, with each mistype a notch beneath it |
| COMBAT | Kills, discharges, splash, auto-shots, damage taken, jams |
| METRIC | Band changes, the Zone, encounters, boss encounter transitions |
| SYSTEM | Weapon swaps, boss phases |
Keystrokes deliberately never render as individual chips: ten seconds of fast typing would be an unreadable picket fence, so a word is one bar and a flurry reads as a handful of them. Records of one type inside a quarter second coalesce into one chip with a multiplier. The bar carries lane filters, live tail, wheel zoom from two seconds to three minutes, drag to scrub, click to decode a record, and an NDJSON export of the whole run.
8. Encounters: events, mini-bosses and bosses#
A wave counter that only ever spawns faster is a difficulty curve, not a rhythm. Encounters are the rhythm: three tiers of escalation that interrupt the ordinary run at different scales, each worth more than the minute before it.
8.1 The director#
makeDirector() decides what fires and when. It owns no rendering and no spawning: the game asks it for numbers and draws the banner itself.
| Rule | Value |
|---|---|
| Never during a boss wave | A boss IS the event |
| Never two at once | One encounter at a time |
| Cooldown | At least 3 waves since the last encounter |
| Guaranteed introduction | Wave 3 always fires the Cargo Convoy, a gift with a banner on it, never a test |
| A ladder, not a flat deck | Every event has a minWave it cannot fire before, so the punishing ones wait until ordinary waves are familiar |
| Otherwise | clamp(0.22 + wave * 0.02, 0.22, 0.55) chance per wave |
| Mini-bosses | Only from wave 6, with a rising share of the pool |
| Wave | Chance an encounter fires | Mini-boss share of the pool |
|---|---|---|
| 3 | guaranteed | 0% |
| 6 | 34% | 15% |
| 10 | 42% | 24% |
| 13 | 48% | 32% |
| 16 | 54% | 39% (capped) |
| 17 and up | 55% (capped) | 39% |
Every choice is drawn from the run's seeded generator, so two players in a Versus room meet the same events on the same waves with no server dealing them. Nothing in js/games/events.js reads Math.random.
8.2 Events#
Short and frequent. A modifier plus a reward multiplier. The spawn block is a patch applied over the game's own numbers for the duration, so the game never branches on an event id.
| Event | Fires from | Duration | XP mult | Chip mult | Weight | What it does |
|---|---|---|---|---|---|---|
| Salvage Window | wave 11 in Home | 16 s | 2.2x | 1.6x | 8 | A SALVAGE WINDOW. It no longer replaces the wave with unavoidable rock: it doubles the opportunity cadence (still capped) and the ordinary wave carries on underneath it. Every rock is still built from your weakest keys, and every one of them is optional |
| Ion Interference | wave 5 | 22 s | 2.0x | 1.4x | 9 | Typos add 50% more heat while active; cooling and heat capacity help |
| Blackout | wave 11 in Home | 20 s | 2.5x | 1.6x | 7 | Uncommitted targets show only their opening letter |
| Scout Flights | wave 4 | 20 s | 1.8x | 1.4x | 10 | Short drones and couriers, with acquisition priced and a 10% formation pressure discount |
| Overdrive Window | wave 7 | 15 s | 2.4x | 1.8x | 6 | Twice the normal clean-kill bonus while active |
| Precision Protocol | wave 4 | 25 s | 2.2x | 1.5x | 8 | Double typo heat and triple clean-kill bonus while active |
| Cargo Convoy | wave 1 | 28 s | 1.6x | 3.0x | 6 | Larger ships with short escorts; completed work earns triple scrap while active. Guaranteed on wave 3 |
An event’s bonus window ends when its active timer expires. Its multipliers pay only for work completed while active; surviving an idle timer earns nothing. Heat and clean-kill modifiers reset immediately on expiry. The already planned ships retain their words and composition until cleared. The banner states the event’s actual effect, and expiry says “bonus window complete.”
8.3 Mini-bosses#
A stationary enemy with escorts and two or three transmissions. It trades fire until defeated or your hull reaches zero. It has no event timeout.
| Mini-boss | Profile | Lines | Length band | Attack pattern | Initial escort | Shield word | XP mult | Chip mult | Weight |
|---|---|---|---|---|---|---|---|---|---|
| Raider | Unremarkable | 2 | short | 2 × 4 burst; 4 damage with drone cover | 3 drones | none | 2.0x | 1.8x | 10 |
| Harvester | Plated | 3 | short | 10 damage cannon; 5 damage with cruiser cover | 2 splitters | none | 2.2x | 2.0x | 9 |
| Warden | Volatile | 2 | medium | 7 damage pulse; 2 × 4 volley | 2 drones | BREACH | 2.4x | 2.0x | 7 |
A mini-boss's escort and shield are attached to its first line only. Its transmissions run 38 to 60 characters at the short band, 144 to 200 at medium, and it carries 16 short lines and 9 medium ones, so a mini-boss you meet three times in an evening has said something different every time.
8.4 Bosses#
The milestone, every fifth wave (every fourth in the Rift), is a named, multi-phase fight that tests more than sustained paragraph typing. A boss has a typing target 10% above its selected tier's nominal pace. Mechanical phases require original typed work that high boss-damage weapons cannot bypass.
Mechanical phase rotas#
| Boss | Ordered mechanical phases |
|---|---|
| Sentinel | Twin shield relays; Break the formation; Precision weakpoints |
| Hive Mind | Break the formation; Precision weakpoints; Twin shield relays |
| Dreadnought | Precision weakpoints; Twin shield relays; Break the formation; Precision weakpoints |
| Oracle | Twin shield relays; Precision weakpoints; Break the formation |
Twin shield relays. A generator on each side must be typed to open a limited core exposure. The left uses left-hand QWERTY words; the right uses right-hand words. Below nominal 60 WPM each uses one word; at 60 or above each uses two. They are visibly marked and immune to offensive charge shortcuts. A closed window restores the generators and repairs 4% of the core's fixed initial maximum health, bounded by that maximum. Mistakes and word-goal forecasts never increase that maximum or enlarge future repairs. Core health can reach zero mid-word, but the phase waits for that word to finish. An expired exposure also lets the current word finish before closing. Boss attacks continue during that word; a second word cannot extend the opening. The reading surface names and highlights the closing word, then the protected transition previews the returning generators without counting trailing keys. The exposure window is clamped to 5 to 18 seconds and priced against selected typing demand. Recreated generator work and reissued core text carry no fresh reward positions.
Every new generator, formation target, weakpoint and echo draws fresh real vocabulary from the shared lexicon and an expanded hand-specific word list. A separate seeded picker remembers words across the run, exhausts eligible unused vocabulary before recycling the least recently used words, and avoids matching target initials where possible. Returning shields draw new words; the deliberate Rewind mistake penalty still resets the same original words. Each replacement preserves its slot's character count, so vocabulary variety does not alter the forecast workload, attack budget or reward eligibility.
Break the formation. Distinct groups contain a typed-only Ward, a Brood ship and a Runner. A Brood can create an extra echo after a typing mistake, at most twice for that target. The extra enemies are pressure, not bonus score, XP or scrap. Regular charge tools can clear eligible unprotected enemies; boss damage bonuses never apply to formation ships. Below nominal 100 WPM there are two groups; at 100 or above there are three.
Precision weakpoints. Each group presents a marked Rewind target and a Weakpoint, both typed-only. A Rewind typo can reset its original words, at most twice. The wrong input still needs correction, and every original character position can earn a reward once. The same two/three-group threshold applies.
Boss attack timing is frozen from the selected challenge and the priced mechanical workload, never recalculated from the player's live WPM. Bosses hold position and repeat visible volleys; there is no global WPM deadline. Higher WPM usually reduces exposure to attacks. Accuracy reduces recovery and extra work. Damage to an exposed paragraph core is capped at 1.35 per newly typed character after equipment/profile modifiers, preserving a limited weapon advantage without allowing a boss weapon to erase the mechanical phase.
The arrival, and the reward review#
A boss used to spawn straight into its first phase: the transmission panel, the health bar, the descent and the damage clock all began in the frame the player was still finishing an ordinary wave in.
Arrival is now two steps. bossIntro builds the encounter, lays out the panel where the typing will happen, names what has arrived and states its first attack and defensive profile, and STOPS. Nothing runs: no clocks, no descent, no damage, no escorts. The boss wave sends no ordinary craft at all and any optional rocks still drifting are swept up first, so nothing competes with it. The Begin control arms after a protected entrance of 1,000 ms, and only a DELIBERATE action leaves: a fresh Enter, or the button. Ordinary letters and Space are consumed and score nothing, because they are typing and typing is exactly what the player was doing a moment ago.
Victory is resolved once, damaging actions stop immediately, the residual escorts are despawned with no kill credit and no drops (finishing off three drones after the boss is dead is an anticlimax and unpriced work), the encounter is SETTLED (17.4), and then bossVictory shows what was actually banked: the salvage, the duplicates and their conversion, the cores, the first clear, and the newly unlocked restart wave. It is frozen and has no timer. The player reads it for as long as they like, at a cost of exactly nothing.
Between phases there is no dialog. A paragraph reissue or an unexpected phase change gets a brief protected visual handoff (380 ms) without advancing combat, and the previous input batch is cleared so its tail cannot become mistakes on the next passage. Ordinary continuing characters inside one passage are never interrupted.
The input barrier#
An 800 ms timeout is not a barrier: a 90 WPM player beats it without noticing, so they finish a boss, keep typing for a beat, and the reward screen they never saw is gone, with those characters scored against their accuracy.
Crossing a protected boundary bumps an INPUT GENERATION. Everything about the old context dies with it: the correction buffer, the keyboard catcher's contents, the lock, and any multi-character batch a virtual keyboard is midway through delivering (the generation is checked between every character and the remainder is discarded). Keys physically held at the crossing are CARRIED: neither their release nor an auto-repeat of them may confirm, and a key has to be let go of and pressed again. Confirmation is a fresh Enter that is not a repeat and not an IME commit, or a pointerdown that BEGAN on the armed control in this generation. It changes state and returns: the same event may never also type the first letter, spend charge, fire a flare or dismiss the next panel. Tab is left to normal focus navigation so a keyboard user can reach the button, and blur or a hidden tab clears the held-key tracking so focus loss cannot leave a card permanently unconfirmable.
| Boss | Profile | Repeating attacks (base damage, before armour) | Phases |
|---|---|---|---|
| The Sentinel | Unremarkable | 8 cannon; 2 × 5 volley; 5 damage with 2 drones | 3 |
| Hive Mind | Swarming | 3 damage with 3 drones; 3 × 2 volley; 3 damage with 2 drones | 3 |
| Dreadnought | Plated | 12 cannon; 3 × 4 broadside; 6 damage with 1 cruiser | 4 |
| The Oracle | Volatile | 8 pulse; 2 × 4 volley; 5 damage with 2 drones | 3 |
Your typing is the weapon. While a transmission is live the turret has no target list: every CHARS_PER_SHOT (6, scaled by the weapon's fire rate, minimum 2) correct characters fires a round that destroys whatever is closest to your base. Type well and the escorts never arrive. Stall, or spray mistakes, and your guns fall silent exactly when the screen is filling up. The answer to "the boss is overwhelming me" is always "type better", which is the answer this whole site is built to reward.
Every boss hit uses hull combat. Armour reduces it and shields absorb it before hull is lost. A nonfatal hit leaves the encounter active.
Boss exchanges and equipment#
Bosses and mini-bosses hold position near the top and repeat visible attack patterns. There is no descent, breakthrough loss, mini-boss timeout, or overtime enrage. Lose only when your hull reaches zero; win by stripping all phases. Timed Versus rounds still end at their agreed round duration.
The attack intention shows the next volley, damage per hit after fitted armour, escorts, and seconds until launch. Shields absorb damage on impact. Cadence is frozen from the selected target pace and priced opening mechanical work: roughly seven volleys over a target-pace full-boss clear, three for a mini-boss, with a minimum four-second cycle. It repeats indefinitely rather than ending the fight. More health and armour buy more exchanges; weapons and accuracy shorten the fight.
| Build or action | Effect during a boss exchange |
|---|---|
| Hull and damage reduction | Survive more incoming hits; attack damage never scales to your maximum health |
| Shields and Shield reserve | Absorb incoming damage; a reserve activated while shots travel still works |
| Phase break | Delays the next volley by a full cycle and applies fitted per-wave hull repair once |
| Precision and pierce | Affect exposed-core damage, capped at 1.35 per new character; no bonus against generators, formations or weakpoints |
| Forgiving weapons | Preserve output; after-typo boss damage is capped at the clean rate to prevent intentional-mistake rewards |
| Area / splash | A typed escort volley can intercept up to three / two nearby escorts |
| Fire rate | Controls the correct-key cadence of escort volleys |
| Cooling | Reduces heat and overheating interruptions |
| Repair / Slow | Repair trades attack typing for hull recovery; Slow halves the boss attack clock and its projectiles |
| Flares / Nuke | Remove eligible escorts; typed-only Wards, Rewinds, Weakpoints and generators resist offensive shortcuts |
Boss reinforcements respect a five-hostile cap. Escorts launch in nearby formations; their interception time includes shield commands and scales to baseline typing. A missed escort deals its class's base damage (drone 6, cruiser 11, splitter 7), without multiplying it by an unused word stack. Queued ordinary-wave spawns wait during a mini-boss. Repair never holds boss escorts or volleys. Phase breaks repair hull, while shields remain a finite reserve. Weapon swaps do not clear accuracy recovery. Stored capacitor charge is retained for normal waves. The recovery counter is shown below the passage.
8.5 Vulnerability profiles#
The reason a loadout is a counter-pick rather than a preference. A weapon choice that is simply "how I like to play" is a personality quiz; a weapon choice that is right for the Dreadnought and wrong for the Hive Mind is a decision.
| Profile | Name | Clean damage | Fumbled damage | Splash | Pierce | Prefers |
|---|---|---|---|---|---|---|
standard | Unremarkable | 1.0x | 1.0x | 1.0x | 1.0x | nothing in particular |
armoured | Plated | 1.0x | 1.0x | 0.35x | 1.5x | precision |
swarm | Swarming | 1.0x | 1.0x | 1.9x | 0.7x | pressure |
volatile | Volatile | 1.0x | 0.75x | 1.0x | 1.0x | forgiveness |
The profile belongs to whatever encounter owns the field, so the Dreadnought's plating shrugs off a Flak Battery even while you are shooting its escorts.
Nothing enforces the counter-pick. loadoutAdvice() tells you in one sentence whether what you are carrying suits what is coming, and bossSchedule() puts the rota on the pre-flight screen so you can choose before the run. Bringing the wrong thing makes a fight harder, never impossible, which is the same stance sectors take about power.
8.6 Laps: the roster cycles#
Bosses arrive in order and the roster repeats. Each full lap raises the boss:
| What scales per lap | How |
|---|---|
| Name | Gains a Roman numeral (Dreadnought II) |
| Attack damage | +22% of base volley damage per lap |
| Scrap | +35% per lap |
| Escort size | +1 craft per initial phase escort per lap, subject to the five-hostile cap |
| Phase health | Unchanged (it is the transmission's length) |
So wave 25 is the Sentinel again but meaner, rather than a boss nobody has met.
8.7 What an encounter pays#
Current Starfall scores all defeated enemy and boss objectives through the same measured target-performance formula. Neither an event multiplier nor a flat boss/phase/first-kill bonus grants additional XP or score. A powerful weapon cannot turn untyped boss text into performance rewards. Already paid positions, regenerated objectives and mistake-spawned echoes never pay again.
Cleared events have base combat salvage 18, mini-bosses 40, and bosses 120 + 40 * completedPhases, through the shared salvage scale from section 4.3. Enemy kills also pay their original carried-work salvage value. Separate encounter receipts still grant the authored loot, duplicate conversions, cores, first-clear tally and checkpoint once at victory. The run report itemizes those receipts rather than paying them again. A failed encounter grants no clear receipt. Legacy eventBonusXp/eventBonusScrap remain compatibility helpers for older run formats, not the current Starfall reward source.
9. Gear, weapons and builds#
Nine mounts, seven categories, fifty-three items. The catalogue is SIDEGRADES, not a ladder: every top-end item is better at what it does and worse at something else. Remove the downsides and the build system collapses into a shopping list where the best item is simply the most expensive one, which tools/check-modules.mjs checks for.
9.1 The slots#
| Slot | Name | What it decides |
|---|---|---|
weapon | Primary | What a correct character is worth, clean and after a slip. The accuracy bargain |
attachment | Primary attachment | Tunes the primary toward precision or toward forgiveness |
weapon2 | Secondary | The other answer, one Tab away. Fitting one reduces heat capacity |
attachment2 | Secondary attachment | Tunes the secondary |
hull | Hull | How much punishment the base takes before it is over |
targeting | Targeting | Mistake forgiveness, combo protection, and earlier reveals |
coolant | Coolant | Heat capacity and how fast it bleeds off. Fewer jams |
reactor | Reactor | Combat salvage and the pace of the field |
module | Module | One utility of your choosing. The panic buttons live here |
A fresh profile owns and wears the starter of every slot, and the second hardpoint defaults to EMPTY rather than to the starter weapon: a one-weapon build is the default and the second mount is something you choose to fit.
What is fitted is the button that changes it. Clicking the item in a slot opens the slot picker: one popup listing everything that slot accepts, from the Armory's loadout and from the Arcade's pre-flight alike, because wanting a different weapon while reading the boss rota that made you want it is the ordinary case and walking to another tab to do it loses the sector, the rota and the launch button. It is the same list either way, and the screen behind it repaints with the new power and pace rather than being replaced.
The picker defaults to Owned. Each compact tile shows its icon, name, rarity border, upgrade level and equipped/owned status. Browse all gear reveals the complete catalogue, with search, availability and name/price/progression sorting. Weapons remain sidegrades; nothing ranks them by damage or power.
Hover or focus updates a shared inspector without changing equipment. Clicking or pressing Enter keeps an item selected; moving outside the grid restores that selection after previewing another tile. Escape dismisses a transient preview, then closes the picker. Touch uses the same inspect-then-equip flow.
The comparison is always visible. Selected and currently equipped items have separate adjacent panels with the same ordered rows. The row set is shared across all candidates for that slot so numbers do not jump around while browsing. Numbers describe the resulting build, including other gear, upgrades and gambits, with that mount active. Unchanged values remain visible. Green/red signed differences identify gains and losses; downward arrows mark stats where lower is better. All numerical effects are outside expanders. Optional item descriptions remain under About these items. An empty secondary is labelled as empty, never as the primary weapon.
The action names the destination mount and explains which item is replaced. The old item remains owned. Moving an item from another mount also identifies the source and what will occupy it afterward. Invalid moves cannot leave a primary weapon empty. Buying shows the Quartermaster's actual price and the currency shortfall, and the wallet stays visible in the dialog header.
Item power. Every item carries one number, on the same scale as the ship's own rating and a sector's recommendation: what that item alone is worth over the bare baseline, out of the same weights, stated at the upgrade level you own it at. So "Titan Frame, 49" reads directly against "the Rift recommends 92". It is signed and it is not floored at zero: an item that trades rating away for something the rating cannot see is an ordinary thing in this catalogue (a Chain Driver at -2 is buying combo scaling, a Glasswing at -4 is buying speed with hull), and a number that could only ever go up would be the ladder this catalogue is not. Two opposite weapons land within a point or two of each other on purpose, because they really are the same amount of gear pointed in different directions; what tells them apart is the axis.
9.2 What an item does, in words#
Every equippable item states itself in plain sentences wherever it is shown: on the Armory cards, in the picker, at the vendors, on the pre-flight hardpoints and (as a hover) on the kit icons. A stat row is only readable by somebody who already knows the mechanics, and the mechanics are exactly what a new player is trying to learn, so "+0.55 clean, -0.55 foul" becomes "banks 55% more charge per correct character while the word is CLEAN" and "banks 55% less after a slip, so a fumbled word earns you almost nothing".
Those sentences are DERIVED from the same stat deltas the game reads (explainGear() in js/gear.js), never written beside an item, so an item that is retuned cannot end up described by last month's numbers. Cosmetics get the same treatment and their sentence is the honest one: it changes how something looks and nothing else.
Beside each sentence is the same delta as a signed chip, gains in green and penalties in red. The two are separate tables in separate files (STAT_WORDS in js/gear.js, STAT_INFO in js/modes/picker.js) and nothing forces them to agree, so a stat that reaches only one of them draws no chip at all and says so to nobody. That has happened: the capacitor stats and the renamed flare stat were described in words while advertising "no effect either way" in numbers. tools/check-modules.mjs now compares the two key sets and fails on the difference by name.
9.3 The stat sheet#
The game reads the aggregate, never the items, so a new stat is one entry here plus one use in the game.
| Stat | Base | Clamped to | Meaning |
|---|---|---|---|
dmgClean | 1 | 0.5 to 3.2 | What a correct character is worth to the meter while this word is unfumbled |
dmgFoul | 1 | 0.2 to 2 | The same after a slip on this word |
chargeRate | 0.4 | 0.2 to 0.65 | Letters of charge per point of the two above. Well under one on purpose: see 7.5. Area weapons pay for their reach here |
chargeCap | 20 | 6 to 128 | Letters of charge the active mount holds |
blast | 0 | 0 to 0.45 | Discharge radius in playfield widths. Above zero makes it an AREA weapon, which spends blastCost per shot |
stall | 240 | 60 to 600 | Milliseconds the turret cannot fire after a mistype |
splash | 0 | 0 to 1.4 | Fraction of a kill's word length paid into the nearest other threat's word |
pierce | 1 | 0.4 to 2.6 | Multiplier against bosses and mini-bosses |
comboDmg | 0 | 0 to 0.02 | Extra charge per point of combo, capped at +60% |
fireRate | 1 | 0.5 to 2 | Multiplier on characters per shot in a transmission (lower is faster) |
maxHp | 100 | 40 to 400 | Base health |
dmgMult | 1 | 0.35 to 1.4 | Multiplier on damage taken |
regen | 0 | 0 to 30 | Health restored per wave or boss phase cleared |
heatCap | 100 | 50 to 320 | Heat before the turret jams |
heatDecay | 20 | 8 to 80 | Heat bled per second |
comboKeep | 0 | 0 to 0.85 | Fraction of the combo kept through a miss |
assists | 0 | 0 to 8 | Typos protected from heat and firing delay per wave; correction remains required |
reveal | 0 | 0 to 4 | Extra seconds a word is readable before it is in range |
fieldSlow | 1 | 0.55 to 1.2 | Multiplier on descent speed (below 1 is easier) |
scoreMult | 1 | 0.75 to 1.45 | Legacy compatibility only; never applied to current Starfall performance rewards |
salvageMult | 1 | 0.8 to 1.6 | Scrap and drop quantity |
flares | 0 | 0 to 8 | Emergency flares, which swat the thing about to land whatever the meter says |
shield | 0 | 0 to 140 | One-off absorb at the start of a run |
The two bold clamps are the ones that keep the central claim true. The floor on dmgFoul is the other important one: a fumbled word must always still be payable, or a slip with a Railgun would mean standing there typing forever, which is a punishment rather than a cost. The floors on chargeRate and the ceiling on stall are the same promise for the same reason.
How stats combine. Plain numbers add. scoreMult and salvageMult multiply. dmgMult and fieldSlow are reductions (a +1 upgrade makes a 0.9 into a slightly stronger 0.886, never a larger number). comboKeep takes the best rather than stacking. Only the LIVE hardpoint's weapon and attachment contribute, so carrying two weapons is never a way to add their numbers together.
9.4 Weapons#
The accuracy axis, running from "devastating while clean, useless after a slip" at one end to "never punished, never spectacular" at the other. Read the pairs as bargains, not as a ladder.
The axis is measured from two things, because its two ends are made of different stuff. The precision end is the SPREAD: how much more a correct character banks while the target is clean than after a slip, which is exactly what a Railgun sells. The forgiving end is REACH: splash and blast pay you for words you never aimed at, so a slip on the one in front of you costs less of your output. Blast counts for a little over twice its number, because the widest discharge in the catalogue is 0.34 against a splash that runs to 0.6 and the two should be worth comparable reach at their own tops. The whole thing is scaled so the CATALOGUE spans the track rather than a theoretical maximum spanning it: the baseline sits at zero, a Railgun at 0.97, a Flak Battery at -0.79, and all five bands (Suppression, Forgiving, Balanced, Sharpshooter, Precision) are occupied. Measured on the spread alone, as it was, the entire suppression half of the catalogue read "Balanced" and the left half of every axis track on the site was empty.
| Weapon | Cost | Requires | Stat changes | What it feels like |
|---|---|---|---|---|
| Service Repeater | free | available immediately | no charge generation or capacity | Type each word to destroy it. |
| Capacitor Repeater | Home boss 1 reward | Automatically equipped on first receipt | 0.2 charge per correct key, 20 capacity | Five words typed buy about one skipped word. Shift+Space fires. |
| Longbarrel Cannon | 375 scrap | available immediately | dmgClean 0.4, dmgFoul -0.4, pierce 0.25 | Banks well on a clean word and sulks after a slip. The first step toward precision. |
| Scatter Array | 375 scrap | available immediately | dmgClean -0.05, dmgFoul 0.15, splash 0.35, pierce -0.2 | Barely notices a mistake, and never hits hard. Splashes onto whatever is nearest. |
| Lance | 1,700 scrap | level 12 | dmgClean 0.55, dmgFoul -0.55, pierce 0.35, fireRate 0.1 | A clean stretch hands you free words. A fumbled one hands you none. |
| Flak Battery | 1,625 scrap | level 10 | dmgClean -0.1, dmgFoul 0.2, splash 0.6, pierce -0.35, blast 0.2, chargeRate -0.1 | Charges slowly. Each fixed-price shot clears a word from every marked target and keeps spare charge. Best against crowds, weak against armour. |
| Chain Driver | 1,875 scrap | level 15 | dmgClean -0.12, dmgFoul -0.18, comboDmg 0.012, chargeCap 4 | Weak on its own. Every point of combo feeds it, so it lives on unbroken streaks. |
| Arc Welder | 1,450 scrap | level 9 | dmgClean 0.05, dmgFoul 0.05, heatDecay 9, fireRate -0.1, chargeCap -4 | Steady charge and fast cooling, with a smaller reserve. Spend shots often. |
| Railgun | 4,800 scrap | level 26 | dmgClean 0.9, dmgFoul -0.75, pierce 0.5, fireRate 0.25, salvageMult 1.08 | High charge earnings and boss damage while clean, very low earnings after a typo. Also recovers more salvage. |
| Nova Lance | 18 cores | level 32 | dmgClean 0.75, dmgFoul -0.6, pierce 0.6, splash 0.2, heatCap -18 | Precision and armour penetration with splash support. Mistakes and heat are the price. |
| Bulwark Repeater | 15 cores | level 28 | dmgClean 0.05, dmgFoul 0.3, splash 0.5, maxHp 20, salvageMult 0.95 | Forgives everything, plated, and pays for it in ceiling. The endurance build, finished. |
| Burst Cannon | 1,275 scrap | level 11 | chargeRate -0.09, blast 0.16, blastCost -2, dmgFoul 0.1, dmgClean -0.05, pierce -0.15 | Affordable six-charge shells for small groups. Narrower reach than Flak; weak against armour. |
| Tempest Array | 18 cores | level 31 | chargeRate -0.12, blast 0.34, blastCost 2, chargeCap 10, splash 0.25, dmgClean 0.1, pierce -0.2, salvageMult 0.96 | Wide ten-charge shells and a deep reserve. Save for a crowd; single targets are inefficient. |
What those numbers actually do. With no attachment and no upgrades:
| Weapon | Clean | Fumbled | Splash | Pierce | Meter | Blast | Stall | Axis | Power | Clean characters for one free 7-letter word | Fumbled | The one sentence |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Service Repeater | 1.00 | 1.00 | 0.00 | 1.00 | 0.00 | 0.00 | 240 | Balanced | 0 | Not available | Not available | Typing only: no charge attacks |
| Capacitor Repeater | 1.00 | 1.00 | 0.00 | 1.00 | 0.20 | 0.00 | 240 | Balanced | -15 | 35 | 35 | Never beats the baseline |
| Longbarrel Cannon | 1.40 | 0.60 | 0.00 | 1.25 | 0.40 | 0.00 | 240 | Sharpshooter | 3 | 13 | 30 | Pays off above 88% accuracy |
| Scatter Array | 0.95 | 1.15 | 0.35 | 0.80 | 0.40 | 0.00 | 240 | Forgiving | 3 | 19 | 16 | Pays off below 95% accuracy |
| Lance | 1.55 | 0.45 | 0.00 | 1.35 | 0.40 | 0.00 | 240 | Precision | 3 | 12 | 39 | Pays off above 88% accuracy |
| Flak Battery | 0.90 | 1.20 | 0.60 | 0.65 | 0.30 | 0.20 | 240 | Suppression | 5 | 26 | 20 | Pays off on a crowd, not on accuracy |
| Chain Driver | 0.88 | 0.82 | 0.00 | 1.00 | 0.40 | 0.00 | 240 | Balanced | 3 | 20 | 22 | Pays off above 94% accuracy |
| Arc Welder | 1.05 | 1.05 | 0.00 | 1.00 | 0.40 | 0.00 | 240 | Balanced | 3 | 17 | 17 | Pays off at any accuracy |
| Railgun | 1.90 | 0.25 | 0.00 | 1.50 | 0.40 | 0.00 | 240 | Precision | 6 | 10 | 70 | Pays off above 86% accuracy |
| Nova Lance | 1.75 | 0.40 | 0.20 | 1.60 | 0.40 | 0.00 | 240 | Precision | 9 | 10 | 44 | Pays off above 86% accuracy |
| Bulwark Repeater | 1.05 | 1.30 | 0.50 | 1.00 | 0.40 | 0.00 | 240 | Forgiving | 15 | 17 | 14 | Pays off at any accuracy |
| Burst Cannon | 0.95 | 1.10 | 0.00 | 0.85 | 0.31 | 0.16 | 240 | Forgiving | 6 | 24 | 21 | Pays off on a crowd, not on accuracy |
| Tempest Array | 1.10 | 1.00 | 0.25 | 0.80 | 0.28 | 0.34 | 240 | Suppression | 12 | 23 | 25 | Pays off on a crowd, not on accuracy |
Read the two character columns together: that is the whole design. Every one of these weapons destroys the word you type, on the character you finish it. What differs is what the typing EARNS: a Railgun hands you a free seven-letter word every ten clean characters and effectively never after a slip, while a Flak Battery is steady either way and takes a whole pocket of the field with it when it does go off. The power ratings say they are the same class of item, because power is a statement about gear and not about the pilot.
The last column is accuracyDemand(), and it is the number to choose by if you only read one: the accuracy at which that weapon starts beating the baseline. An area weapon deliberately never wins that comparison, because it is not trying to: it wins by taking three words at once.
9.5 Attachments#
One socket per hardpoint, and its whole job is to move the weapon along the axis. The obvious play is to double down (a Lance with a Focusing Lens); the interesting one is to correct (a Railgun with a Recoil Damper, which is still sharper than anything else and no longer catastrophic when you slip).
| Attachment | Cost | Requires | Stat changes |
|---|---|---|---|
| Empty Socket | free | available immediately | none |
| Focusing Lens | 275 scrap | available immediately | dmgClean 0.2, dmgFoul -0.15 |
| Recoil Damper | 275 scrap | available immediately | dmgFoul 0.28, dmgClean -0.1 |
| Match Actuator | 1,350 scrap | level 14 | dmgClean 0.35, dmgFoul -0.35 |
| Ballast Weight | 1,350 scrap | level 14 | dmgFoul 0.4, salvageMult 0.96 |
| Splitter Prism | 1,200 scrap | level 11 | splash 0.3, dmgClean -0.1 |
| Armour Piercer | 1,525 scrap | level 17 | pierce 0.4, splash -0.15 |
| Overclock Coil | 1,275 scrap | level 13 | fireRate -0.25, heatCap -18, stall 45 |
| Capacitor Bank | 1,100 scrap | level 10 | chargeCap 24, chargeRate -0.02 |
| Reservoir Bank | 2,600 scrap | level 22 | chargeCap 48, chargeRate -0.03 |
| Diffusion Cell | 1,275 scrap | level 12 | blast 0.1, chargeRate -0.04 |
| Tracer Feed | 3,000 scrap | level 19 | comboDmg 0.007, dmgFoul -0.12 |
| Gyro Mount | 1,625 scrap | level 16 | assists 1, dmgClean -0.1 |
| Resonator | 12 cores | level 30 | dmgClean 0.25, splash 0.2, fireRate -0.1, heatCap -15, dmgFoul -0.15 |
The Gyro Mount trades clean-word damage for an assist. Targeting-slot assistance has no score penalty. The Resonator is the one attachment that asks for nothing back, which is why it is priced in cores.
9.6 Hulls#
| Hull | Cost | Requires | Stat changes |
|---|---|---|---|
| Standard Frame | free | available immediately | none |
| Reinforced Hull | 275 scrap | available immediately | maxHp 25, chargeCap -2 |
| Bulwark | 1,275 scrap | level 8 | maxHp 45, dmgMult 0.9, salvageMult 0.97 |
| Aegis Plating | 3,600 scrap | level 18 | maxHp 55, dmgMult 0.85, shield 30, salvageMult 0.94 |
| Mender Frame | 1,875 scrap | level 15 | maxHp 30, regen 9, chargeCap -4 |
| Glasswing | 1,700 scrap | level 14 | maxHp -25, salvageMult 1.254 |
| Titan Frame | 18 cores | level 30 | maxHp 90, dmgMult 0.72, regen 6, shield 25, salvageMult 0.9 |
9.7 Targeting#
Targeting improves mistake forgiveness, combo retention and word visibility without reducing score. Duelist's Reticle instead offers a salvage bonus with less heat headroom.
| Targeting | Cost | Requires | Stat changes |
|---|---|---|---|
| Open Reticle | free | available immediately | none |
| Assist Module | 325 scrap | available immediately | assists 2 |
| Combo Governor | 1,350 scrap | level 10 | comboKeep 0.5, assists 1 |
| Predictive Lock | 3,450 scrap | level 20 | assists 3, reveal 1.2 |
| Duelist's Reticle | 3,900 scrap | level 24 | salvageMult 1.18, heatCap -20 |
| Oracle Sight | 15 cores | level 28 | comboKeep 0.75, assists 4, reveal 2 |
9.8 Coolant, reactor and module#
| Coolant | Cost | Requires | Stat changes |
|---|---|---|---|
| Stock Vent | free | available immediately | none |
| Twin Radiator | 250 scrap | available immediately | heatDecay 10, stall -45 |
| Cryo Loop | 1,275 scrap | level 9 | heatCap 45, heatDecay 14, stall -70, chargeRate -0.03 |
| Run-Hot Manifold | 1,525 scrap | level 13 | heatCap -25, heatDecay -4, stall 80, salvageMult 1.242 |
| Zero-Point Sink | 3,900 scrap | level 22 | heatCap 80, heatDecay 26, stall -110, salvageMult 0.96 |
| Reactor | Cost | Requires | Stat changes |
|---|---|---|---|
| Standard Cell | free | available immediately | none |
| Field Dampener | 475 scrap | available immediately | fieldSlow 0.92, salvageMult 0.96 |
| Overcharged Cell | 1,200 scrap | level 7 | salvageMult 1.12, heatCap -15 |
| Salvage Magnet | 1,625 scrap | level 12 | salvageMult 1.25, heatDecay -3 |
| Flux Capacitor | 4,200 scrap | level 24 | fieldSlow 0.88, salvageMult 1.21, heatDecay -6, chargeCap -4 |
| Singularity Core | 21 cores | level 34 | fieldSlow 0.8, salvageMult 1.44, maxHp -20 |
| Module | Cost | Requires | Stat changes |
|---|---|---|---|
| Empty Mount | free | available immediately | none |
| Chaff Launcher | 350 scrap | available immediately | flares 2 |
| Emergency Shield | 525 scrap | available immediately | shield 45, chargeCap -3 |
| Time Dilator | 1,775 scrap | level 16 | fieldSlow 0.9, reveal 0.8, salvageMult 0.95 |
| Charge Battery | 3,750 scrap | level 21 | flares 4, maxHp 15, heatCap -12 |
| Anchor Protocol | 12 cores | level 26 | comboKeep 0.6, heatDecay 8, salvageMult 0.94 |
| Prospector's Rig | 15 cores | level 30 | salvageMult 1.3, flares 2, maxHp -15 |
9.9 Upgrades#
Every owned item goes to +5. Each level is worth UPGRADE_STEP (14%) of the item's base stats, and an item's DOWNSIDES scale too: a +5 Glasswing is even more fragile, which keeps a sidegrade a sidegrade at every level.
scrap for the step from +n = round(basePrice * (0.35 + 0.30 * n * n))
basePrice for a core item = cores * 420
the last two steps (+4, +5) also cost 2 and 4 cores respectively
| Item | Base price | +1 | +2 | +3 | +4 | +5 | Total scrap | Total cores |
|---|---|---|---|---|---|---|---|---|
| Assist Module | 325 | 114 | 211 | 504 | 991 | 1674 | 3494 | 6 |
| Lance | 1700 | 595 | 1105 | 2635 | 5185 | 8755 | 18275 | 6 |
| Titan Frame | 7560 | 2646 | 4914 | 11718 | 23058 | 38934 | 81270 | 6 |
Upgrading a good item competes with buying the next one up rather than being strictly worse, and the core cost on the last two steps means the very best loadout still has a boss fight standing behind it rather than an afternoon of drills.
9.10 Power#
One number for "how kitted am I", the way an ARPG shows item level. It is compared against a sector's recommendation and shown on the loadout screen. A bare call rates the BETTER of the two hardpoints, because a player who brought the right weapon for half the fight is equipped for the place.
| Stat | Weight per point above base |
|---|---|
maxHp | 0.30 |
dmgMult | -34 |
regen | 1.4 |
heatCap | 0.10 |
heatDecay | 0.55 |
comboKeep | 26 |
assists | 5.5 |
reveal | 6 |
fieldSlow | -46 |
scoreMult | 16 |
salvageMult | 12 |
flares | 7 |
shield | 0.22 |
dmgClean | 15 |
dmgFoul | 13 |
splash | 10 |
pierce | 9 |
comboDmg | 220 |
fireRate | -14 |
Survivability is weighted heaviest because that is what sector difficulty actually tests. dmgClean and dmgFoul are weighted almost equally on purpose: a Lance is only more powerful than a Scatter Array in the hands of somebody who stays clean, and if precision alone raised power the Rift would look gated behind a weapon rather than behind a decision.
9.11 Two hardpoints#
A loadout frozen for a whole run makes vulnerability profiles a gotcha: you meet the Dreadnought's plating with a Flak Battery in your hands and there is nothing to do but remember for next time. Carrying two weapons and swapping with Tab turns that into a decision made DURING the fight.
Three things stop it from being free:
| Cost | Value |
|---|---|
| The swap drops your lock | Immediately |
| Spin-up | SWAP_MS 600 ms, during which damage ramps from SWAP_DAMAGE 0.5 back to 1 |
| Fitting a second hardpoint at all | heat capacity -10, whichever weapon is live; the legacy score factor is not applied |
So a single-weapon build preserves that heat margin. The question is two specialists (answer more situations, trade heat headroom) against one specialist and an empty mount (answer one thing perfectly).
9.12 Gambits#
Opt-in handicaps, taken before a run, that change combat risk and salvage. They never multiply current Starfall performance score or XP. They stack multiplicatively. This is the pressure valve for a player who has out-geared their sector: rather than nerfing their gear, let them BET it.
| Gambit | Multiplier | What it removes |
|---|---|---|
| No Shield | 1.15x | Start with no absorb, whatever your hull says |
| Empty Racks | 1.12x | No emergency flares |
| Hot Start | 1.18x | Every wave begins at half heat |
| Iron Discipline | 1.20x | No assists and no combo protection, whatever your targeting says |
| Paper Hull | 1.35x | Half the base health you would otherwise have |
| Sudden Death | 1.60x | One hit ends the run. Any hit |
Gambits OVERRIDE gear rather than subtracting from it, so "no assists" means none regardless of what is bolted on. Taking all six multiplies rewards by 3.94x, and js/run.js clamps the applied multiplier to 4x.
9.13 The cheat#
Settings carries an Unlock everything button, and it is labelled as a cheat because it is one. It grants the whole gear and cosmetic catalogue through the same paths a drop uses, so an item unlocked this way is indistinguishable from one earned.
It exists for a plain reason: a build system nobody can browse is a build system nobody learns, and nothing on this site is gated anyway, so it takes away no content. What it takes away is the EARNING, which is most of the medium loop. It touches no XP, no level, no leaderboard and no scrap: you can still not afford the upgrades, and your boards still say what you actually typed.
9.14 Four builds that work#
Worked examples, because a stat table does not teach a build. Stat numbers below come from loadoutStats() for that set of items at +0. Charge damage is listed before the separate 1.35 boss-core cap; typed-only objectives ignore that bonus. All four builds leave the performance score and XP formula unchanged.
The Purist. Railgun, Match Actuator, empty second mount, Open Reticle, Glasswing, Run-Hot Manifold, Overcharged Cell, Empty Mount.
| Charge damage | 2.25 clean, 0.20 fumbled (the floor) |
| Survivability | 75 hull, no shield, no flares, no assists, heat capacity 60 |
| Earning | Salvage x1.60, the equipment cap. Performance score and XP unchanged |
| Power rating | 0 |
Power 0 in the Rift, which recommends 92. That is the point: this build is not under-geared, it is geared for a pilot who does not intend to be hit. Add gambits until it stops being easy.
The Long Haul. Bulwark Repeater, Recoil Damper, Titan Frame, Oracle Sight, Zero-Point Sink, Flux Capacitor, Charge Battery.
| Damage | 0.95 clean, 1.58 fumbled. A slip costs almost nothing |
| Survivability | 225 hull, damage taken x0.72, 25 shield, 4 assists, 4 flares, 75% of the combo kept, 2 seconds of reveal, heat capacity 168, field at 0.88x |
| Earning | Salvage x0.99. Performance score and XP unchanged |
| Power rating | 179 |
Its hull, weapon and cooling trade some salvage for endurance; the reactor recovers most of that difference. Oracle Sight cushions mistakes.
The Counter-picker. Lance with an Armour Piercer for the Dreadnought on the primary, Flak Battery with a Splitter Prism for the Hive Mind on the secondary, Combo Governor, Mender Frame.
| Primary | 1.55 clean, 0.45 fumbled, pierce 1.75 |
| Secondary | forgiving and wide, one Tab away |
| Survivability | 130 hull, 9 regen per wave, 1 assist, half the combo kept |
| Earning | Performance score and XP unchanged; the second mount costs 10 heat capacity |
| Power rating | 40 |
The Prospector. Scatter Array, Recoil Damper, Aegis Plating, Assist Module, Cryo Loop, Salvage Magnet, Prospector's Rig, flown in the Rift.
| Damage | 0.85 clean, 1.43 fumbled, 0.35 splash |
| Survivability | 140 hull, damage taken x0.85, 30 shield, 2 assists, 2 flares |
| Earning | Salvage x1.53, which the Rift then multiplies by 2.2. Performance score and XP unchanged |
| Power rating | 72 |
Cores still only come from bosses, so even the farming build is about surviving to fight more of them.
10. Loot and the economy#
10.1 Two currencies#
| Currency | Where it comes from | What it buys |
|---|---|---|
| Scrap | Time and consistency: every run, every contract, every duplicate | Most of both catalogues, and every upgrade |
| Cores | ONLY bosses and mini-bosses. Nothing else in the game produces one | The Archivist's stock, and the last two upgrade levels of anything |
Cores being boss-only is what makes a boss structurally worth starting rather than merely worth more points, and it is what the Archivist's stock is priced in. A contract that any run could finish must never pay one for nothing: the two contracts that do pay cores ask for a boss, a wave 15, or three clean runs.
10.2 Rarity#
Rarity is DERIVED from what an item costs and what it asks of you, rather than being a field on all 56 cosmetic entries. That keeps one source of truth: if an item is expensive or gated behind level 40, it IS rare, and the two can never disagree.
| Rarity | Colour | Derived from | Duplicate value | Base drop weight |
|---|---|---|---|---|
| Standard | #A4B2D2 | free, no requirement | 20% of cost | never dropped |
| Polished | #38DFD2 | anything cheaper than the bands below | 25% | 58 |
| Rare | #8B7BFF | level 10+, or 1,000+ scrap | 30% | 30 |
| Elite | #F2B33D | level 22+, or 2,000+ scrap, or any badge requirement | 35% | 10 |
| Legendary | #FF5D8F | level 40+, or 3,500+ scrap, or a badge plus 2,000 scrap | 40% | 2 |
An item may override this by carrying its own rarity, which is how a cheap thing can be made special deliberately.
10.3 Drop tables#
rolls is how many cosmetic cards the source turns over; scrap and cores are inclusive ranges; floor forces every roll to at least that rarity.
| Source | Cosmetic rolls | Scrap | Cores | Floor | Rarity weights (polished / rare / elite / legendary) |
|---|---|---|---|---|---|
| Event cleared | 0 | 10 to 26 | 0 | ||
| Cargo Convoy cleared | 1 | 60 to 140 | 0 | 78 / 20 / 2 / 0 | |
| Mini-boss killed | 1 | 45 to 90 | 0 to 1 | 62 / 28 / 9 / 1 | |
| Boss killed | 2 | 140 to 280 | 1 to 3 | 40 / 36 / 19 / 5 | |
| Boss killed for the FIRST time | 3 | 220 to 400 | 2 to 4 | rare | 30 / 40 / 24 / 6 |
All drop quantities are then multiplied by the sector's loot multiplier, the loadout's salvage multiplier and any gambit multiplier.
Only a CLEARED encounter drops. An undefeated boss grants no clear loot.
10.4 The three anti-frustration rules#
- A duplicate is never nothing. It converts to scrap at the rarity's rate and the reveal card says so. "You got something you already have, and it was worth nothing" is the fastest way to make a loot system feel hostile. Rolling also PREFERS items you do not own, so the duplicate path is a fallback rather than the common case.
- A pity counter guarantees an Elite or better within
PITY_AT(6) rolls, applied to the FIRST roll of a batch so a guarantee does not become three. It is re-derived from what was actually GRANTED, so a roll that found nothing to give does not quietly eat a step of protection. - A first kill floors at Rare. The first time you beat something should never pay out worse than the tenth.
10.5 The vendors#
Three shopfronts over the two catalogues, because a single undifferentiated shop is a spreadsheet and a shop with a face is a place you visit.
| Vendor | Currency | Stock | Pricing |
|---|---|---|---|
| The Quartermaster | scrap | Everything, always | List price |
| The Salvager | scrap | Five items, rotating at local midnight | 25% off |
| The Archivist | cores | Prestige stock only | Cores, which only bosses drop |
The Salvager's five are drawn from a generator seeded on the DATE, so everybody sees the same five things on the same day with no server dealing them, and the countdown to the restock is the reason to look in tomorrow.
purchase() is the one payment path: it takes payment and grants in one block with nothing between the two lines that can throw, because "paid, not granted" is the one failure a player would never forgive.
10.6 Cosmetics#
Five purely decorative slots, 56 items. Nothing here affects gameplay. "Does this affect the game" is answered by which file an item lives in (js/cosmetics.js or js/gear.js), so the two are never blurred.
A cosmetic gated behind level 40 is legal precisely because it is a RECORD of reaching level 40 and changes nothing about play.
Ships (12)
| Ship | Cost | Requires | Rarity |
|---|---|---|---|
| Standard Issue | free | Standard | |
| Ember | 900 | Polished | |
| Jade Runner | 1,200 | Rare | |
| Nightshade | 1,500 | Rare | |
| Drifter | 1,800 | Rare | |
| Crimson Lance | 2,000 | badge: a 100 combo | Legendary |
| Gilded Arrow | 2,200 | level 15 | Elite |
| Bulwark | 2,600 | level 25 | Elite |
| Void Cutter | 3,200 | badge: wave 20 | Legendary |
| Solar Flare | free | badge: 90 WPM | Elite |
| Phantom | free | Starfall rank 10 | Polished |
| Warden | free | level 40 | Legendary |
Trails (10)
| Trail | Cost | Requires | Rarity |
|---|---|---|---|
| Vapour | free | Standard | |
| Running Dark | free | Standard | |
| Ion Wake | 700 | Polished | |
| Ember Wake | 900 | Polished | |
| Jadewash | 1,100 | Rare | |
| Goldstream | 1,400 | level 12 | Rare |
| Aurora | 1,600 | level 20 | Rare |
| Ghostlight | 2,400 | badge: ten flawless runs | Legendary |
| Solar Wind | free | badge: three bosses in one run | Elite |
| Nova | free | Starfall rank 15 | Polished |
Keycaps (10)
| Keycap | Cost | Requires | Rarity |
|---|---|---|---|
| Slate | free | Standard | |
| Cream | 600 | Polished | |
| Carbon | 800 | Polished | |
| Mint | 1,000 | Rare | |
| Amber | 1,200 | level 10 | Rare |
| Rose | 1,200 | Rare | |
| Dusk | 1,500 | level 18 | Rare |
| Beacon | free | badge: 70 WPM | Elite |
| Ghost | free | badge: no weak keys | Elite |
| Champion | free | level 50 | Legendary |
Name plates (10)
| Plate | Cost | Requires | Rarity |
|---|---|---|---|
| Standard Plate | free | Standard | |
| Steel | 700 | Polished | |
| Beacon Plate | 1,000 | Rare | |
| Amber Plate | 1,300 | level 14 | Rare |
| Rose Plate | 1,300 | Rare | |
| Dusk Plate | 1,600 | level 22 | Elite |
| Void Plate | 2,000 | badge: wave 30 | Legendary |
| Jade Plate | free | badge: 30 curriculum stars | Elite |
| Duellist Plate | free | badge: ten Versus wins | Elite |
| Gold Plate | free | level 60 | Legendary |
Titles (14)
| Title | Requires |
|---|---|
| No title | free |
| Cadet | free |
| Quick Fingers | badge: 50 WPM |
| Surgeon | badge: 99% over 600 characters |
| Regular | badge: seven day streak |
| Wavebreaker | badge: wave 20 |
| Boss Killer | badge: six bosses in one run |
| Scholar | badge: every curriculum node cleared |
| Night Owl | badge: a run between two and five in the morning |
| Centurion | badge: 110 WPM |
| Duellist | badge: fifty Versus wins |
| Marathoner | badge: thirty day streak |
| Collector | badge: twenty thousand scrap earned |
| Legend | level 80 |
Every title is free: a title is a record of something, so charging for it would make it a purchase instead.
11. The Training Grounds#
The Training tab is two ladders stacked. The Academy on top teaches the keyboard; Flight School below it is the curriculum, which teaches speed. They are scored on deliberately different axes and neither gates the other.
11.1 The Academy#
"Slow is smooth, and smooth is fast."
Coverage and accuracy, never speed. A player who certifies the whole keyboard at 22 WPM has done exactly as well as one who did it at 90, because what is being measured is whether every key is under your fingers and whether you hit it correctly, which is the thing speed is built on top of later. There is no WPM, no latency and no time anywhere in js/academy.js. If you find yourself adding a speed term to that file, it belongs in js/skill.js.
Twenty one groups across four stages. The key pool is CUMULATIVE: a group's drill material may use its own new keys plus everything earlier in the ladder, which is why the drills turn into real words surprisingly early and why nothing already learned goes stale.
| Stage | Group | Keys added | The idea |
|---|---|---|---|
| Home Keys | Index Anchors | f j | The bumps. Find them without looking and everything else has an address |
| Home Keys | Middle Fingers | d k | One step in from the anchors |
| Home Keys | Ring Fingers | s l | The weakest fingers on the strongest row |
| Home Keys | Little Fingers | a ; | The outside edge |
| Home Keys | The Stretch | g h | The index fingers reach inward. The hand does not move |
| Home Keys | The Whole Home Row | asdfghjkl; | All ten mixed. The first real checkpoint |
| The Top Row | r and u | r u | Up, and back down to the bumps |
| The Top Row | e and i | e i | Two of the most common letters in English |
| The Top Row | w and o | w o | Ring fingers up |
| The Top Row | q and p | q p | The far corners. Everybody is slow here |
| The Top Row | t and y | t y | The inner reach |
| The Top Row | Top Row and Home | qwertyuiop | Two rows mixed. Most short English words are now reachable |
| The Bottom Row | v b n m | v b n m | The index fingers go down |
| The Bottom Row | c and comma | c , | Middle fingers down |
| The Bottom Row | x and full stop | x . | Ring fingers down |
| The Bottom Row | z and slash | z / | The last two corners |
| The Bottom Row | Every Letter | a to z | The whole alphabet in real words. The second checkpoint |
| The Whole Board | Capitals | A to Z | Shift with the opposite hand, never the same one |
| The Whole Board | Punctuation | , . ' " ; : ! ? - | What turns typed words into typed sentences |
| The Whole Board | The Number Row | 1 to 0 | The row nobody practises, and the one that slows real work most |
| The Whole Board | Symbols | !@#$%^&*()[]{}<>/=+_~ plus backslash, vertical bar and backtick | The last thing between you and the diploma |
Drill material. Real words wherever the cumulative pool can spell them, because a beginner typing "dad" and "flask" is learning something a beginner typing "fjfj djdj" is not. Roughly half the items in a drill must contain one of the group's NEW keys, or a wide pool would quietly stop teaching the group it is named after. The earliest groups fall back to short alternating chunks, because there is no English word in "f j" alone. Nothing repeats twice in a row: a repeat reads as a stutter and stops testing the transition into the key.
Certification is judged from the SHARED per-key model, and that is what makes testing out honest:
| Bar | Value | Why |
|---|---|---|
keySamples | 20 presses | Before the model is allowed an opinion about a key at all |
keyAcc | 90% | EVERY key in the group must clear this. The strict half |
groupAcc | 95% | One completed drill must have reached this |
reps | 1 | At least one completed drill on the group |
The per-key floor is what "completeness" means: not "the group averaged 95" but "there is no key in here you are still missing". And the evidence is read out of state.keys rather than kept locally, so a key drilled in the arcade, or measured by the Flight Check, counts exactly as much as one drilled here.
That is the whole route a proficient typist takes. The placement seeds real per-key data, testOutCandidates() lists every group whose keys already clear the bar, and one clean drill certifies each. They are through in a few minutes and into the arcade, where their time is better spent.
A beginner walks the ladder, and groupStatus().blockers tells them in plain words what is still missing ("3 keys not practised enough", "q, p below 90%", "a drill at 95% or better"), because a beginner should never read a bare "not certified".
Nothing here is gated and graduation unlocks nothing. Every group is startable on the first visit, in any order. Certifying one is worth 300 XP, the largest flat award outside a boss. Graduating the whole keyboard is worth 3,000 XP, the largest flat award anywhere, because that is the one big number a slow typist can reach on their first evening. What graduation produces is a diploma(): recognition, a date and a duration. Not a key.
11.2 Flight School: the curriculum#
Twenty nodes in five groups, an ordered walk rather than a set of locks. Every node is playable from minute one; what the walk provides is an ORDER and a recommendation, which is a different service from a gate.
| Group | Node | Material | Characters | Signpost pace |
|---|---|---|---|---|
| Foundations | Home Row Anchors | home | asdfjkl; | 18 |
| Foundations | The Home Row | home | asdfghjkl; | 22 |
| Foundations | Reaching Up | homeTop | top row plus home | 25 |
| Foundations | Reaching Down | letters | bottom row plus home | 28 |
| Foundations | The Whole Alphabet | letters | a to z | 32 |
| The whole keyboard | Common Words | common | 36 | |
| The whole keyboard | Capitals and Shift | capitals | A to Z | 38 |
| The whole keyboard | Everyday Punctuation | punct | ,.'";:!?- | 40 |
| The whole keyboard | The Number Row | numbers | 1234567890 | 42 |
| The whole keyboard | Symbols and Brackets | symbols | shifted row, brackets, slashes | 45 |
| Real text | Longer Words | commonLong | 48 | |
| Real text | Sentences | prose | 52 | |
| Real text | Paragraphs | prose | 56 | |
| Real text | Code | code | (){}[];=<>_. | 55 |
| Precision | Clean Sweep | common | 45 | |
| Precision | Your Trouble Keys | generated live from your model | 40 | |
| Precision | Awkward Pairs | generated from your worst bigrams | 44 | |
| Speed | Sprint Burst | common | fifteen seconds flat out | 70 |
| Speed | Two Minute Cruise | prose | 74 | |
| Speed | The Long Haul | prose | five minutes without a fade | 78 |
The signpost pace is not a gate and not what stars are measured against. Stars come from beating your own baseline (section 4.2). The goal number is there so the card can say what this material is usually comfortable at, and so the recommender can tell that a 110 WPM typist has no business being sent to the home row (OUTGROWN, 1.3x measured speed).
The two adaptive nodes are never skipped. Your Trouble Keys and Awkward Pairs build their material from the model, so their difficulty tracks whoever is typing: a 120 WPM typist still has a weakest key and a pair their fingers argue about, and drilling those is exactly what is left to do at that speed.
12. Flight Check: the sprint#
One surface doing three jobs.
12.1 The three jobs#
- The sprint. A timed typing test, a quote, or a drill on your own weakest keys. It reports through
finishRun()like every other game, and the two timed modes carry leaderboards. - The drill surface. Every Academy lesson and every curriculum node runs here. The Training Grounds hands it an explicit word list plus a meta tag, and that tag rides into the RunResult so the funnel scores the right thing. An Academy drill leads its results screen with accuracy and the per-key breakdown rather than a WPM number, because that is what it was judged on.
- The placement. See section 4.5. It runs a 60 second sprint and then grants, seeds and reports, and it deliberately does NOT go through
finishRun().
12.2 The modes#
| Mode | Clock | Material | Ranked |
|---|---|---|---|
| 60 seconds | 60,000 ms | Mixed everyday words | Yes |
| 15 seconds | 15,000 ms | Mixed everyday words | Yes |
| Quote | untimed | One of 20 real passages, punctuation and capitals attached | No |
| Weak keys | 45,000 ms | 46 chunks built from the keys the model says are letting you down | No |
Why the timed passage is NOT adaptive, when everything else on the site is: a board where each player typed material chosen for them is a board that compares nothing. So the timed modes and the placement all draw the same mixed passage of everyday words. Personal material lives in the Weak keys mode and in the Training Grounds, where nothing is ranked against anybody else.
Passage length for a timed run is sized at 22 characters per second, which is 264 WPM: comfortably past the fastest human on record, so nobody can reach the end of it.
12.3 Reading and the caret#
Three lines are visible and the caret rides the second one, so there is always a line of what is coming next below it. A fast typist reads ahead instead of typing into a wall.
A gap longer than 2,000 ms is treated as somebody answering the door rather than a slow reach, and is not folded into a key's mean latency.
13. Versus#
Head to head racing, for signed-in players with a claimed screen name.
13.1 Why Firestore and not the worker#
Everything else on this site talks to a Cloudflare Worker whose store is KV: eventually consistent, which is exactly right for a leaderboard and exactly wrong for a race. Two people typing against each other need each other's progress inside a few hundred milliseconds and they need it PUSHED. Firestore gives both, on the identity pool the site already authenticates against, so Versus costs no new service.
Four collections, no composite indexes: typePlayers (a presence card per account), typeLobbies (the room code IS the document id, so a shared link is a direct lookup), typeGames and typeChats.
13.2 The two formats#
Nobody referees. Both formats are built from a SEED in the game document and every client builds the identical race from it through mulberry32.
| Format | Length | Built from the seed | Scored on |
|---|---|---|---|
| Starfall | 90 s | The whole spawn schedule up front: 4 + min(9, wave) threats per 11 second wave, each with its word, entry position and descent rate | Points |
| Sprint | 340 characters, hard stop at 210 s | The same passage | Net WPM |
A threat's position is a pure function of the shared clock (spawn moment, descent rate) rather than something integrated frame by frame, so a phone that dropped ten frames is still looking at exactly the same sky as the laptop it is racing. A client that reloads mid-race meets everything that fell while it was away in one frame: those words really did land on its base, so they cost what they cost, but they are not drawn or sounded.
Each client grades itself, which is safe because there is nothing secret here: the word lists ship in js/words.js either way, so a server holding the answers would be guarding a door with no wall attached. What the design DOES guarantee is that nobody can write anybody else's numbers, and that is enforced by the Firestore rules (a seat is a key in a map and only its owner may touch it) rather than by good manners.
13.3 Race tuning#
| Constant | Value |
|---|---|
| Room code | 5 characters from ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (no I, O, 0 or 1) |
| Seats per room | 6 |
| Countdown | 5,000 ms |
| Starting hull (starfall format) | 100 |
| Wave length (starfall format) | 11,000 ms |
| Threat damage | 7, or 13 for a long word |
| Seat heartbeat | 15,000 ms |
| A seat is not counted after | 46,000 ms of silence |
| Host considered gone after | 90,000 ms |
| Progress pushed | Every 500 ms, throttled |
| Chat | Last 40 lines, 240 characters each |
13.4 What a race never touches#
Not the skill model, not the curriculum, not state.games, not the Academy, not History, not a solo leaderboard, and it does not go through finishRun() at all. A race is a different measurement (a shared seed, other people's pressure, nothing chosen by the player) and letting it feed the same numbers would quietly make two things one.
The only account-level trace is state.mp:
| Field | Meaning |
|---|---|
played | Races finished |
won | Races won, which requires at least one opponent |
points | Ranking points: max(1, playerCount - yourPlace), or 1 for a solo race. Raw scores are not summed because a starfall score and a sprint WPM are different units |
banked | The last 40 game ids, so a reload cannot count a race twice |
Four versus badges read that tally on the next completed SOLO run, and a win may post to the versus wins board if the player has opted into leaderboards.
13.5 The four reliability rules#
Each was a bug first on the sibling site, and every one is load bearing:
- A listener's first snapshot comes from the local cache and can be a version behind (a transaction never lands locally), so nothing destructive may act on a snapshot until
vsFresh()says the server confirmed it. - A dotted
updateDocCREATES the map it writes into, so every seat write goes throughvsTouchSeat, a transaction that only writes a seat that still exists. Because that is a server read every fifteen seconds it doubles as the backstop for a wedged lobby listener. - Firestore's error callback is TERMINAL, so every long-lived listener is attached through
vsWatch, which re-opens with backoff and never gives up on a connection error (only on a rules one). Everything re-opens at once onvisibilitychangeandonline, because backoff measured against a sleeping clock is the wrong shape. - A result is banked exactly once per account, guarded by
state.mp.bankedand persisted.
14. The meta layer#
The loops below the run. Everything in js/run.js closes inside a single run; everything here is what makes tomorrow worth turning up for. Each piece is a named idea rather than decoration, and nothing in it is a lock: a contract you ignore costs you nothing, a broken streak takes nothing away that you already had, and ascension is opt-in.
14.1 Contracts#
The Zeigarnik effect: an unfinished, clearly stated task nags in a way an open-ended one does not. Three daily and one weekly, each a single sentence with a number in it, seeded from the date so everybody gets the same set with no server dealing them. The three dailies are drawn without replacement and never two that track the same thing, because three "type N characters" contracts is one contract wearing three hats.
| Daily contract | Goal | Scrap | Cores |
|---|---|---|---|
| Keep Typing | 1,200 correct characters | 120 | |
| Long Shift | 2,500 correct characters | 220 | |
| Three Sorties | Finish 3 runs of anything | 140 | |
| Hold the Line | Reach wave 10 in Starfall | 180 | |
| Deep Patrol | Reach wave 15 in Starfall | 260 | 1 |
| Bounty | Bring down a boss | 200 | 1 |
| Clearing Work | Kill 2 mini-bosses | 170 | |
| Steady Hands | Finish a run at 97% accuracy or better | 200 | |
| Smooth Is Fast | Finish 3 runs at 97% or better | 300 | 1 |
| Unbroken | Reach a combo of 60 | 190 | |
| In the Zone | Spend 45 seconds in the Zone | 240 | |
| Academy Business | Certify an Academy key group | 220 | |
| Range Time | Finish 2 Academy or Training drills | 150 | |
| Storm Chaser | Clear 3 field events | 180 | |
| Time Trial | Finish a Flight Check sprint | 130 |
| Weekly contract | Goal | Scrap | Cores |
|---|---|---|---|
| The Long Week | 15,000 correct characters | 900 | 2 |
| Boss Hunter | Bring down 5 bosses | 1,100 | 3 |
| Into the Deep | Reach wave 25 in a single run | 1,000 | 3 |
| Academy Term | Certify 4 key groups | 900 | 2 |
| Precision Week | 10 runs at 97% or better | 1,000 | 2 |
| Flow State | 5 minutes in the Zone | 1,200 | 3 |
Wave and combo contracts take the BEST single run rather than a sum, which is what the wording promises. A finished contract waits to be CLAIMED: collecting a reward you can see is worth more than one that lands silently, and the claim is the reason to open the panel tomorrow.
14.2 The streak#
Loss aversion, deliberately blunted. A streak that shatters after one missed day punishes exactly the person who was ill, and they do not come back.
| Rule | Value |
|---|---|
| Advances | Once per local day, on any completed run |
| Insurance earned | One token every 5 days kept, capped at 3 |
| Insurance spent | Automatically, on the first day you miss |
| What insurance does | Preserves the streak at its current length rather than growing it |
| Daily bonus in training / legacy formats | min(200, 20 + days * 12) scrap and min(500, 60 + days * 28) XP; current Starfall tracks the streak without this flat award |
The bonus flattens because a bonus that keeps growing turns a game into an obligation.
14.3 The comeback bonus#
The likeliest moment to lose somebody is the run after a long gap, so that run pays extra instead of showing them how much they missed. After 5 days away, the first completed training or legacy-format run pays clamp(round(120 * min(days, 30) / 5), 120, 700) XP. Current Starfall keeps performance XP tied to the defeated targets and adds no comeback XP.
14.4 The Zone and ascension#
Covered in sections 7.7 and 4.7. Total time in the Zone accumulates in state.meta.zoneMsTotal and feeds the weekly Flow State contract.
15. Achievements#
Sixty two badges, each a single pure predicate over the state and the run that just finished. checkBadges() stamps state.badges itself, so a badge can never be granted twice. Badges earned in current Starfall are recorded without flat badge XP. In training and legacy formats, badge XP lands on the ACCOUNT only, never on a game's own XP: a game rank is a measure of how much of that game you have played, not of how many account-wide milestones happened to fall while you were playing it.
Mileage
| Badge | XP | Requirement |
|---|---|---|
| First Flight | 100 | Finish your first run |
| Ten Sorties | 150 | Finish ten runs |
| Fifty Sorties | 300 | Finish fifty runs |
| Hundred Sorties | 500 | Finish a hundred runs |
| Five Hundred Sorties | 1,000 | Finish five hundred runs |
| Ten Thousand Keys | 200 | Press ten thousand keys |
| A Hundred Thousand Keys | 450 | Press a hundred thousand keys |
| A Million Keystrokes | 1,200 | Press a million keys |
Levels
| Badge | XP | Requirement |
|---|---|---|
| Recruit | 100 | Level 5 |
| Ensign | 150 | Level 10 |
| Lieutenant | 250 | Level 20 |
| Commander | 400 | Level 30 |
| Ace | 700 | Level 50 |
| Vanguard | 900 | Level 75 |
| Grandmaster | 1,200 | Level 100 |
Speed
| Badge | XP | Requirement |
|---|---|---|
| Thirty Club | 120 | A run at 30 WPM or better |
| Fifty Club | 200 | 50 WPM |
| Seventy Club | 320 | 70 WPM |
| Ninety Club | 500 | 90 WPM |
| Hundred and Ten | 750 | 110 WPM |
| One Thirty | 1,100 | 130 WPM. Professional pace |
Accuracy
| Badge | XP | Requirement |
|---|---|---|
| Not One Miss | 250 | A run of real length with no mistyped character |
| Ten Times Flawless | 600 | Ten flawless runs banked |
| Surgical | 400 | 99% accuracy over six hundred characters |
| Perfect Wave | 200 | Clear a whole wave without one mistyped key |
| Five Perfect Waves | 450 | Five such waves in one run |
| Steady Hand | 300 | 97% accuracy for a full minute of sprint |
The arcade
| Badge | XP | Requirement |
|---|---|---|
| Wave Five | 120 | Reach wave 5 |
| Wave Ten | 220 | Reach wave 10 |
| Wave Twenty | 450 | Reach wave 20 |
| Wave Thirty | 800 | Reach wave 30. The words stop being words |
| Boss Down | 200 | Your first boss |
| Three Down | 350 | Three bosses in one run |
| Fleet Breaker | 700 | Six bosses in one run |
| Chain of 25 | 120 | A 25 character combo |
| Chain of 50 | 250 | A 50 character combo |
| Chain of 100 | 500 | A 100 character combo |
| Chain of 200 | 900 | A 200 character combo |
| On Fumes | 400 | Finish a run with a tenth of your base left |
| Not a Scratch | 500 | Reach wave 10 without losing any hull |
| Perfect Storm | 900 | Wave 10 with a hundred combo and not one mistyped key |
Learning
| Badge | XP | Requirement |
|---|---|---|
| First Clear | 150 | Clear your first curriculum node |
| Ten Stars | 250 | Ten curriculum stars |
| Thirty Stars | 500 | Thirty stars |
| Full Sweep | 800 | Clear every node at least once |
| Perfect Curriculum | 1,200 | Three stars on every node |
| No Weak Links | 700 | Every key you type often at 95% or better |
| Old Enemy | 450 | Bring a key you used to miss constantly back to 97% |
| Flight Checked | 100 | Take the Flight Check and get placed |
Everything else
| Badge | XP | Requirement |
|---|---|---|
| Quartermaster | 200 | Earn a thousand scrap |
| Outfitter | 400 | Earn five thousand scrap |
| Collector | 800 | Earn twenty thousand scrap |
| Contender | 200 | Play ten Versus games |
| First Blood | 200 | Win your first Versus game |
| Ten Wins | 450 | Win ten Versus games |
| Fifty Wins | 900 | Win fifty Versus games |
| Three Day Streak | 200 | Play three days in a row |
| Seven Day Streak | 400 | Seven days in a row |
| Thirty Day Streak | 1,000 | Thirty days in a row |
| Full Roster | 500 | Finish a run of every game in the arcade |
| Night Owl | 300 | Finish a run between two and five in the morning |
| Second Wind | 400 | Come back after a month away and set a personal best on the first run |
16. Leaderboards, accounts and identity#
16.1 The boards#
The Leaderboards tab opens on Starfall wave score, with a separate wave scrap category. Sector, combat difficulty, numbered challenge tier and wave selectors compare like with like. Both also carry the rules version performance-v1. The two independent personal bests may come from different attempts, and each column shows the measured WPM and accuracy from its own attempt.
| Board family | Ranked on | Scope |
|---|---|---|
| Starfall wave score | Best performance points on a cleared wave | Rules, sector, difficulty, tier, wave |
| Starfall wave scrap | Best credited combat salvage on a cleared wave | The same exact scope |
| Sprint, 60 / 15 seconds | Net WPM | Sprint duration |
| Starfall Cadet / Pilot / Ace / Nightmare (legacy) | Earlier cumulative points | Earlier adaptive rules only |
| Starfall deepest wave (legacy) | Earlier depth | Earlier adaptive rules only |
| Account level | Account level, backed by lifetime XP | Account |
| Versus wins | Races won | Account |
Progress has a Starfall wave-record card and a direct Compare on leaderboards button. The save keeps 600 wave scopes with the most recently improved bests, bounded for cloud sync. Existing history, gear, currency and legacy bests stay intact; old aggregate totals cannot be invented into per-wave records.
Strictly opt-in. Posting needs the existing opt-in and signature. A cleared wave is submitted when a finished run banks, including earlier clears in a run that later ends in defeat. A half-finished wave or abandoned run is excluded. Checkpoint attempts rank within their exact wave scope. Opting in or signing in can resync saved bests; pending sends recheck consent and identity.
The shared big-world-quiz Worker owns two new routes: POST /typing/wave-score with up to 64 wave samples in one batch, and GET /typing/wave-board with a versioned board identifier. New data uses its own typing:wave: KV namespace. It never shares earlier typing:board: Starfall scores. The server validates the whole batch before writing, checks finite bounded counters and duration, recomputes WPM and accuracy, and bounds score by the maximum per-character performance reward. Score and scrap preserve separate winning evidence and server timestamps. Replays keep existing equal or higher bests.
These are plausibility checks for a local client, not an authoritative replay or proof against a modified client. Verified names prove account identity, not competitive input integrity. Guest and account storage keys are separated. Every remote name is escaped before rendering. Worker KV remains eventually consistent, so simultaneous submissions are subject to that backend's limits.
The Worker is a separate deployable. Until its update is available, local records work and the new boards show an availability error. A client rejects a response without the matching rules version and board, rather than displaying an incompatible legacy table. Existing sprint, level, versus and legacy board routes retain their earlier behavior.
16.2 One identity, three save files#
The same Firebase project authenticates Big World Quiz, Big Piano Quiz and Big Typing Quiz, so one account and one verified screen name work everywhere. Progress does NOT: this site syncs to its own collection.
| Site | Firestore collection |
|---|---|
| Big World Quiz | users/<uid> |
| Big Piano Quiz | pianoUsers/<uid> |
| Big Typing Quiz | typingUsers/<uid> |
Screen names come from the shared registry at /name on the worker, which is the source of truth. SN_NAME_RE and SN_RESERVED in js/state.js mirror the worker's rules and are mirrored a third time in big-piano-quiz; the worker will refuse anything they let slip.
Everything works signed out. Firebase is lazily imported on first use, the account area is hidden entirely when it is not configured, and a fully local profile loses nothing except cross-device sync and Versus.
17. Data model and persistence#
Everything the player owns lives in ONE localStorage blob under typingQuiz.v1, and the whole blob is what syncs to Firestore.
| Branch | Contents | ||
|---|---|---|---|
profile | Placement record: placed, placeOffered, placeWpm, placeAcc, placeLevel | ||
xp | total, level, scrap, cores, earnedScrap | ||
gear | owned (id to level), equipped (slot to id), gambits | ||
loot | opened, pity, best, byRarity | ||
meta | Contracts, streak, ascensions, lastRunAt, zoneMsTotal | ||
games | Per game: xp, rank, plays, timeMs, best by mode key, badges, bosses | ||
keys, bigrams | The per-key model, capped at 140 and 400 entries | ||
curriculum | Per node: stars, plays, bestWpm, bestAcc, baseWpm | ||
academy | Per group: reps, chars, bestAcc, certified; plus graduated | ||
skill | runs, chars, ewmaWpm, ewmaAcc, bestWpm, bestAcc; plus baseline (the PARAGRAPH baseline: wpm, source, acc, durationMs, at, confidence) and contexts (observed prose / words / phrase / random pace, kept BESIDE the baseline and never blended into it) | ||
progress | Starfall's own versioned progression: `starfall['<sector> | <difficulty> | tier:<N>'] (plus retained legacy scopes) = { cleared: [wave...], every }, the receipts grant ledger that makes an encounter's reward idempotent, and one pending` receipt whose save failed |
badges | id to { at, n } | ||
cosmetics | owned list and equipped per slot | ||
history | Newest first, capped at 200 runs | ||
waveRecords | Up to 600 versioned sector/difficulty/tier/wave scopes, each with separate bestScore and bestScrap evidence | ||
settings | Sound, music, difficulty, sector, nullable challengeTier (1 to 19 after recommendation), keyboard, caret, motion | ||
lb | Leaderboard opt-in, signature, country | ||
mp | Versus tally and banked game ids |
Three rules hold this together:
normalizeState()is the only source of shape guarantees. Any path that replacesstatewholesale (boot, cloud adopt, clear all data) runs it, it is safe to run twice over the same object, and every new field is added there as well as infreshState().saveState()is the ONE writer. It stampsupdatedAtand pokes the cloud through a hook, so a mutation can never quietly skip the sync.- The blob has to stay small, because it syncs. That is why History, keys, bigrams, grant receipts and banked race ids are all capped.
17.4 Encounter settlement#
A boss pays when it dies, not when the run does, and it pays once. js/encounter-rewards.js is the service; js/run.js reads the receipts it left rather than rolling anything of its own.
| Rule | Why |
|---|---|
The encounter id is run : sector : difficulty : wave : kind : id : #occurrence | A retry is a new run and a new id; a replayed UI callback, a second Continue press or a reload resolve to the SAME id and grant nothing again |
| The seed is FROZEN into the receipt | A re-roll is not merely refused, it is impossible: there is nothing left to roll |
| First-kill eligibility updates AS EACH ENCOUNTER SETTLES | Two bosses of one kind in one run used to both read "first kill" off the same stale snapshot |
| Loot, currency, the first-clear tally, the checkpoint and the receipt are ONE update, then one save | Half of a settlement is worse than none |
| A failed save keeps the receipt PENDING and retries the same identifier | Never a fresh roll; the victory screen says so rather than pretending |
finishRun ITEMISES the receipts and adds nothing | Calling the roll again there is exactly the double payment this exists to prevent |
| Rewards already settled stay earned when a later wave kills the player | An intentional refinement of the older all-at-run-end behaviour |
What it does NOT promise is global exactly-once across devices. js/cloud.js reconciles whole state documents (it takes the newer one, it does not merge fields), so two devices settling the same encounter concurrently is a conflict resolved by whichever document is newer, exactly as every other field is. A real global transaction would need a backend design, and claiming one here would be a lie.
Pity, duplicate conversion, repeat-boss loot and the first-kill floor are all unchanged: they live in js/loot.js and the service calls them.
Per-identity slots. A signed-in account stores under typingQuiz.v1:u:<uid>, so two people sharing a browser do not share a save file, and signing out returns you to the local one.
18. For developers#
18.1 The shape of the repo#
No build step, no bundler, no test suite, no package.json, and no third-party code of any kind: no npm, no CDN, no vendored library, no import map. The deployable output IS the repo root, served as-is by Cloudflare Pages. The one dependency this repo ever had (Three.js and troika, for the retired 3D view) went with it.
The one thing that is generated rather than hand-written is bible.html, and it is committed too, so nothing has to be run before a deploy. Media lives under assets/ (art in assets/art/, the four music tracks in assets/music/) and none of it is load-bearing: both are skins over a game that plays without them.
Three pages, sharing one design language and no stylesheet:
| Page | Stylesheet | Script | Depends on the app |
|---|---|---|---|
index.html (front page) | css/home.css | js/home.js | No. It imports nothing from js/, fetches nothing and touches no localStorage |
app.html (the game) | css/app.css | js/app.js (ES modules) | It is the app |
bible.html (this document) | css/bible.css | js/bible.js | No. Same isolation rule as the front page |
The isolation is the point: a broken module cannot take down the landing page or the manual.
18.2 The module graph#
js/ is one module per concern, acyclic. js/app.js is the entry point: it imports every module, holds the router and init(), and wires the cross-module hooks. Nothing imports it back.
| Layer | Modules |
|---|---|
| Pure helpers | util.js (imports nothing) |
| State | state.js |
| Rules | xp.js, words.js, skill.js, academy.js, gear.js, loot.js, meta.js, vendors.js, achievements.js, cosmetics.js |
| Presentation | art.js (the asset registry and its vector fallback), music.js (the four-track score) |
| Services | audio.js, cloud.js, board.js, nav.js |
| The funnel | run.js |
| Games | games/registry.js, games/starfall.js, games/sprint.js, games/events.js, games/threat.js, games/eventlog.js, games/debugpanel.js, games/parallax.js |
| The difficulty contract | games/typing-effort.js (what text costs to type), games/wave-plan.js (what a wave is, before it is flown), games/combat-feedback.js (what the equipped build is actually doing) |
| Settlement | encounter-rewards.js (idempotent encounter rewards and the checkpoint record) |
| Boss surface | games/transmission-view.js (persistent character nodes for the transmission) |
| Panels | modes/arcade.js, modes/train.js, modes/progress.js, modes/ranks.js, modes/armory.js |
| Multiplayer | versus.js |
| Shell | app.js |
Because modules run in strict mode with their own scope, the few shared mutable bindings are reassigned through exported setters (setState, setCurrentStorageKey, setApplyingRemote, setCurrentTab), and the subsystem cycles are broken by hooks that app.js wires at init():
| Hook | Breaks the cycle |
|---|---|
setSyncHook | so state.js never imports cloud.js |
setIdTokenProvider | so board.js never imports cloud.js |
setAfterAuth, setBusyProbe | so a remote copy is never applied on top of a live run |
setSwitchTab, setPaintLevelChip, setRefresh* | so a panel can drive the shell without importing app.js |
18.3 The invariants#
These are the ones an edit breaks by accident. Each is stated as the thing that must remain true.
finishRun()is the one funnel. It is the only writer of XP, scrap, cores, the skill model, badges, gear grants, per-game bests, History, contracts and leaderboard submissions. If a game needs a reward it does not grant, add it there.- Only complete runs count.
abandonRun()is the single exception and it takes ONLY the keystrokes. normalizeState()owns the state shape, andsaveState()is the one writer.#kbCatchis persistent and must never be rebuilt or re-parented. A phone raises its keyboard only for a focused input.- Nothing is ever gated. Where that would let somebody keep something they did not earn (opening a run deep), the answer is to PRICE it in the funnel and say so on the screen, never to lock it.
- Current Starfall word tiers follow fixed challenge pace and wave depth. Personal skill still recommends training and a starting challenge.
- The Academy never scores speed.
- The placement and the Academy head start only ever RAISE.
- Gear must never multiply XP.
- Every strong gear item keeps a downside.
- Cores come from bosses and mini-bosses and nowhere else.
- The performance ledger pays each original target position at most once. Encounter clear receipts remain separately idempotent.
- Loot rolls only on CLEARED encounters.
- Cosmetics never affect gameplay.
- The seeded PRNG is what lets Versus work without a referee. Introducing a bare
Math.randominto a seeded path silently desynchronises a race. - Never render un-escaped user data. The rule is about the PATH a string takes, not about who wrote it, so authored catalogue strings go through
escapeHtmltoo. _headersnames every origin the page may reach. A new external dependency is two edits, not one.- Sound effects are synthesized, music is streamed, and the line between them is the point. Every effect is made at runtime in
js/audio.json one compressor bus, created on the first user gesture and never at import time, because a typing game fires a sound on every keystroke and a decoded buffer per hit would stall exactly the sound that must never stall. The four music tracks are real files, fetched only once music is on and a scene is set. Do not add an audio FILE for anything that fires from a keydown handler. - The Google Fonts stylesheets must stay async. A pending cross-origin stylesheet blocks script execution, and this has shipped as a bug on a sibling site where it read as "every link is broken".
- A renderer reads the run and never writes to it. The rule outlived the second view it was written for and keeps its teeth for the reason underneath it: a decorative draw that consumed the simulation's randomness would desynchronise a Versus race by being pretty.
- The art is a skin, never a requirement. Every sprite call is gated on
spriteOk()with the hand-drawn shape behind it, and the game must play identically withassets/art/deleted. - There is no third-party code, no
vendor/, no npm, no bundler, no CDN and no import map. Adding some is a real decision. - The registry is the only list of what games exist.
- The threat meter is feed-forward. It runs inside the simulation so its timing is deterministic, but no gameplay code may read its output back, and it may never draw from a seeded stream. A seeded replay has to be byte-identical whether the meter runs or not. Its character counts stay at the honest baseline, or the number stops being weapon-independent and stops being the thing the player was promised.
- Pause is the clock. It works because the run's
now()freezes, so every timer stops together. Do not add a timer that reads the wall clock directly, and do not "fix" a subsystem by adjusting its timestamps on resume: that is the bug this design exists to make impossible. - The event log never allocates and never stores a string. It is on during every run at keystroke rate, so a record is typed-array stores and nothing else. Formatting belongs to the overlay and the export.
- A peripheral cue carries its information without hue and without motion. Every state that reaches the player through the edge of vision also reaches them through shape, thickness, extent or position, and every animated cue has a static form that says the same thing. Reduced motion is never reduced information.
- One difficulty contract, and every schedule is built from it. The threat meter, the wave planner, the boss attack cadence, the pre-flight preview and the diagnostics all price work through
js/games/typing-effort.js, in paragraph-equivalent characters, against the run's FROZEN baseline. Three separate estimators used to answer the same question three slightly different ways. Do not add a fourth, and do not multiply an effort weight on top of a rate fraction: they are the same fact stated two ways, and applying both is the double-counting the model exists to remove. - The paragraph baseline is not the rolling average.
ewmaWpmblends every completed run, which is the right evidence for picking a word tier and the wrong evidence for scheduling a wave, because it is the number the effort model is supposed to be EXPLAINING. Only a Flight Check or the player's own hand may writeskill.baseline, and it is frozen for the length of a run: a player typing well clears faster rather than meeting a field that accelerated to cancel their improvement. - The Home Sector is answerable with the starter single-target weapon, at every depth, through every event and every boss lap. Area damage helps and is never required; a random power-up helps and is never required. It is policy DATA (
SECTOR_POLICY) rather than scattered wave-number conditions, and it is enforced after an event has patched the wave and after children are budgeted, because those are the two places it used to leak. - An opportunity is optional, and optional means it costs nothing to ignore. A reward rock is excluded from the wave budget, from the required pace, from the wave-clear test and from auto-fire's target list, and it damages the ship only by physically hitting it. Only an asteroid can originate a temporary power-up.
- A protected card freezes the SIMULATION, not the spawner.
now()freezes, so heat does not decay, an event does not expire, a buff does not drain and no time-based reward accrues. That is what makes an indefinite reward review safe. - A protected card is left by a deliberate action, never by typing. Crossing the boundary bumps an input generation and clears everything about the old context; ordinary letters and Space are consumed and score nothing; a key held across the crossing has to be released and pressed again. An 800 ms timeout is not a barrier, because a fast player beats it without noticing.
- Nothing about a character's state may change its box. On any reading surface: colour, background and inset shadows are free; display, width, padding, border, letter-spacing, weight, transforms and swapping the text itself are not. See 18.5.
- A boss pays when it dies, exactly once, through
js/encounter-rewards.js, with the seed frozen into the receipt.finishRunitemises those receipts and grants nothing again. - A checkpoint comes from a saved clear. Not from a formula, not from a highest-wave number, not from an aggregate boss count and not from a legacy setting. Every entry point resolves through
resolveStartWave. - The sound describes the equipped build, and audio computes nothing.
js/games/combat-feedback.jsprojects one authoritative result per action from the resolved stats the damage itself used;js/audio.jsrenders it. A second copy of the equipment rules in the sound layer is a second copy that drifts. Pitch means an ABSOLUTE bonus percentage, never a combo count modulo anything, and a weapon with no streak stat plays no streak melody.
18.4 How to add things#
A new mini-game. Write a module under js/games/ exporting meta, start(host, opts), stop() and ideally isRunning() and snapshot(). Add one entry to js/games/registry.js. Report the result through finishRun(). Nothing about the panel, the router or the scoring funnel changes. A soon entry is an announcement and must read as one.
A new sprite. Drop the file under assets/art/ in the directory its prefix implies and it resolves through artPath() with no code change; a key that needs a different home goes in the SPECIAL map in js/art.js. Draw it through sprite()/spriteOk(), never through a raw Image, and leave the vector shape behind it working. ART_DIRECTION.md states what it has to look like.
A new threat. One entry in KINDS in js/games/starfall.js (radius, damage, word length range, kill score, spawn weight, first wave, and any of the weak, splits or drops behaviour flags). If it needs a behaviour none of those cover, add the flag next to them rather than branching on the id in the step loop.
A new gear item. One entry in GEAR in js/gear.js with slot, cost or cores, an optional req, and stats as deltas from BASE_STATS. Give it a downside, or tools/check-modules.mjs is entitled to fail. If it needs a stat that does not exist, add it to BASE_STATS, give it a clamp in loadoutStats(), a weight in POWER_WEIGHTS, and one use in the game.
A new event or boss. One entry in EVENTS, MINI_BOSSES or BOSSES in js/games/events.js, plus its lines in TRANSMISSIONS or MINI_LINES. A boss needs a profile from PROFILES and a brief explaining the counter-pick, because the pre-flight screen shows both. Never read Math.random in that file.
A new badge. One row in BADGES in js/achievements.js: an id, a name, a description, an XP value and a pure predicate. It is stamped automatically and can never be granted twice.
A new word tier. One entry in TIERS in js/words.js whose words stay strictly inside the tier's own chars, plus its place in TIER_ORDER. tools/check-modules.mjs asserts every tier the curriculum names exists.
A new weapon's projectile identity. One entry in WEAPON_ID in js/games/starfall.js naming a shape family from WEAPON_IDENTITY.md, a core and halo pair, and the one-line trait the pause legend shows. It must differ from every other weapon on shape or motion as well as colour, and its halo must stay out of the reserved hues (the threat ramp, the charge teal, the damage red, the boss violet). A genuinely new SHAPE is a new draw function beside the eight, plus its reduced-motion form, plus its row in that document.
A new event type in the log. One id in EV and one row in EV_NAMES and EV_SLOTS in js/games/eventlog.js, then put it in a lane in js/games/debugpanel.js. Payloads are three integers and a float: encode, do not stringify.
18.5 Verifying a change#
Serve the repo over http (the file protocol will not do: a browser refuses a module script from an opaque origin) and drive it with Playwright against the pre-installed Chromium. Two checkers stand in for a test suite, and both exit non-zero on the first failure:
| Command | What it proves |
|---|---|
node tools/check-modules.mjs | The arithmetic the progression design rests on: skill out-scales gear, targeting assistance preserves score, a sector advises and never blocks, an abandoned run banks nothing, every curriculum tier exists in the lexicon, every Academy group builds real material, weapons are bargains rather than a ladder, both hardpoints behave, the threat meter's whole acceptance table (section 7.14), the event log's ring, filters and export, the effort model's ordering and its infeasibility answer, a plan's structural contract and the Home policy, encounter settlement's idempotence and the checkpoint rules, and that the combat feedback matches the damage the same action deals |
node tools/check-starfall-balance.mjs | The question the other two cannot answer: CAN THE WAVES BE FLOWN, at the pace they promise, with the starter weapon and nothing else. 100 seeds per baseline (20, 40, 60, 90, 120 plus the supported edges) over four complete boss blocks, flown by a deterministic agent that reads before it types and pays a fresh acquisition on every switch; plus every forced event variant at depth, and the sector policy checked structurally. It DERIVES ITS OWN demand arithmetic, because a test that validates a helper by calling the same helper validates nothing |
python3 tools/check-app.py | Every module parses and imports, the router reaches every tab, a banked run moves the level and survives a reload, the placement only raises, all three art styles work, a real run can be PLAYED a word at a time, pausing freezes the sim tick and a key pressed during it moves nothing, checkpoints are offered only where they were earned and never on a boss wave, a boss opens on a protected intro that ordinary typing cannot dismiss and a held Enter cannot confirm, ADVANCING THE CURSOR THROUGH A BOSS PASSAGE MOVES NO GLYPH, an ignored asteroid costs nothing and never collides, an encounter settles once and survives a reload, the log records the run and its provenance filter holds, F8 opens and closes the timeline, and all three pages have no page errors or missing files |
Module bindings are not global, so page.evaluate(() => state) cannot reach them. Two doors exist instead: window.BTQ in js/app.js is the deliberate test seam, read by nothing in the app, and localStorage.getItem('typingQuiz.v1') is the persistence path.
| Seam | Answers |
|---|---|
state(), save(), clearAll() | The save file |
finishRun, switchTab, openFlightCheck | Drive the shell and the funnel |
games, nodes, nextNode, weakKeys, skill(), level(), cloud() | The catalogues and the measurements |
sfField() | What the running game is asking for right now: the next character, whether the capacitor can fire, what is on the field. It is how the checker PLAYS rather than hammers |
pause(reason), resume({instant}), isPaused(), getSimTick() | The pause surface. The tick is the honest test: it is the log's clock, so if it moves while paused the whole pause story is false |
getThreat() | The threat meter's output block |
getEventLog(opts), exportLogNDJSON(), onEvent(cb) | The flight recorder, which is the test ORACLE: it says what the run actually did, rather than what the screen appears to say |
sfTx() | The boss paragraph as the SCREEN has it: the exact string on the nodes, the cursor, and the bounding box of every character. It exists for one assertion that cannot be made any other way, and which every other kind of test was blind to: advancing the cursor must not move a single glyph |
A LAYOUT BUG NEEDS RECTANGLES, not a screenshot and not a DOM shape. The boss paragraph's old renderer made the current character an inline-block with a min-width and substituted a NBSP for the current space. Both are legal, both render, neither throws, and both MOVE THE TEXT: an atomic inline hands the line breaker a wrap point on either side of it, so a word near the right edge split at whichever letter the cursor was passing through and re-joined a keystroke later. Nothing but measuring every character's box at every cursor position catches that. The rule it produced is worth keeping in mind for any reading surface: NOTHING ABOUT A CHARACTER'S STATE MAY CHANGE ITS BOX. Colour, background and inset shadows are free; display, width, padding, border, letter-spacing, weight, transforms and swapping the text itself are not.
18.6 Deploying, and the build stamp#
Two deployables, and only one of them is in this repo:
| Deployable | Where it lives | Where it goes |
|---|---|---|
| The static site | this repo root | Cloudflare Pages |
| The backend Worker | the big-world-quiz repo | Deployed from there, serving all three sites |
Adding or changing a /typing/* endpoint is a commit in that other repo. Neither deploy implies the other is live.
Two files in the repo root are read by Cloudflare Pages rather than by a browser, and neither is reproduced by a plain local serve, so both have to be tested by deploying them or by serving with an equivalent handler:
| File | What it does |
|---|---|
_headers | Security headers and the Content-Security-Policy that names every origin the page may reach |
_redirects | The two renderer addresses, /3d and /2d |
Every deploy is stamped. All three pages carry a short content hash in the footer, written by node tools/stamp.mjs and visible as plain text (no JavaScript involved, so it still reads when the JavaScript is the broken thing). It is a hash of the DEPLOYABLE OUTPUT rather than a git commit, because a stamp recording the current commit cannot be committed: writing it changes the tree, which changes the commit.
Before committing:
node tools/build-bible.mjs # if GAME_BIBLE.md changed
node tools/stamp.mjs # always
node tools/check-modules.mjs
python3 tools/check-app.py
Both checkers fail on a stale stamp, deliberately: a stamp somebody forgot to regenerate is worse than none at all, because it states confidently that a stale deploy is fresh.
18.7 This document's own pipeline#
GAME_BIBLE.md is the source. tools/build-bible.mjs renders it into bible.html, which is committed like everything else, so the site still has no build step at serve time. The generator owns the page shell, the contents sidebar and the anchors; css/bible.css and js/bible.js own the look and the search. node tools/build-bible.mjs --check exits non-zero when the page is out of date with the markdown, and tools/check-modules.mjs runs that check so a stale page cannot ship quietly, exactly as with the stamp.
19. Balance reference#
Quick answers to the questions balance arguments actually start with.
19.1 How long does a level take?#
The following table describes TRAINING and legacy pace XP, not current Starfall. Assuming continuous typing at the stated pace and 95% accuracy, pace XP only (no flat bonuses, no events, no gambits):
| Net WPM | XP per minute | Minutes from level 1 to 10 | To level 20 | To level 42 (the placement cap) |
|---|---|---|---|---|
| 25 | 12 | 292 | 1,371 | 7,351 |
| 40 | 36 | 97 | 457 | 2,450 |
| 60 | 94 | 37 | 175 | 938 |
| 80 | 184 | 19 | 89 | 479 |
| 100 | 311 | 11 | 53 | 284 |
| 130 | 576 | 6 | 29 | 153 |
In training a beginner reaches level 10 far faster than that table suggests, because flat bonuses dominate their early runs: a first run, a first day, a first node clear, a first Academy certification and a couple of wave milestones is over 1,000 XP before any pace XP is counted, and level 10 costs 3,508.
That gap is the design working. Read the two columns together: the expert's XP is almost all pace, the beginner's is almost all flat, and neither is being paid for something they did not do.
19.2 What does the placement save?#
In the training pace model, a 100 WPM typist is placed at level 22, which is 20,398 XP, which is about 65 minutes of continuous play at their own pace. A 130 WPM typist is placed at level 30, which is 41,115 XP, or about 70 minutes at theirs. That is the whole argument for the Flight Check existing: without it, the first hour of an expert's time is spent re-proving something one minute already proved.
19.3 Does gear out-earn skill?#
Current Starfall gear contributes exactly 1x to performance score and XP. Difficulty, tier, combo, the Zone, FOCUS and ascension also contribute 1x. They can change survival and the number of enemies defeated, and salvage equipment can increase scrap, but they do not increase points for the same measured original typing work.
At a fixed output of original characters, 80 WPM pays twice 40 WPM before the accuracy factor. At sustained equal accuracy and enough enemies available, twice the speed also clears twice the original work, producing approximately four times the XP per minute. This relationship is bounded by the 260 WPM reward ceiling, finite work, typing-time measurement and the one-second sample floor. It is an illustrative rate, not a guarantee about every wave or build.
19.4 How much does accuracy cost?#
Two separate penalties, applied in different currencies:
| Accuracy | XP kept (squared) | What it costs in the arcade |
|---|---|---|
| 100% | 100% | Nothing. Clean words die early with a precision weapon |
| 95% | 90% | Corrections take time, core damage enters recovery, and marked mechanics may add work |
| 90% | 81% | Heat is a constant background concern |
| 85% | 72% | Jams start happening in the middle of waves |
| 70% | 49% | Frequent corrections, heat and extra work substantially reduce progress |
20. Glossary#
| Term | Meaning |
|---|---|
| Assist | A typo protected from heat and firing delay; correction is still required. Refills per wave. Targeting-slot assists have no score penalty |
| Axis (accuracy) | Where a loadout sits between precision (a huge clean number and a weak fumbled one) and suppression (flat either way, and paid for words you never aimed at). Measured from the clean/fumbled spread minus splash and blast |
| Item power | What one item alone is worth to the power rating, on the same scale a sector recommends. Signed, and not floored at zero: an item may trade rating for something the rating cannot see |
| Band (boss) | Which length of transmission a boss phase uses: short, medium or long |
| Band (threat) | One of the five severity levels, from CALM to OVERRUN, that the threat number's pressure falls into. Shown as pips, the pill's border and the screen edges |
| Scrap | The soft currency. Time and consistency, not speed |
| Clean | A target that has not been fumbled during this engagement |
| Combo | Consecutive correct characters |
| Core | The hard currency. Only bosses and mini-bosses produce one |
| Checkpoint run | A Starfall run opened at an earned block. Cleared waves set scoped score/scrap records; skipped depth grants no depth badge |
| Encounter | An event, a mini-boss or a boss. Anything the director stages |
| Fumbled (foul) | A normal word you have mistyped on, or a boss in its ten-key accuracy recovery window |
| Gambit | An opt-in combat handicap with a salvage multiplier; no Starfall performance multiplier |
| Hardpoint | A weapon mount. There are two, swapped with Tab |
| Heat | Rises on mistypes. Full heat jams the turret |
| Jam | 1,150 ms during which the turret takes no input at all |
| Ledger | Per-target typing evidence and once-only reward receipts; also the separate encounter-clear grant record |
| Lock | The threat your keystrokes are currently going to |
| LOST | A threat nobody could still save, struck through on its plate and taken out of the threat number. Still typeable, just no longer worth your keystrokes |
| Pressure | The threat demand divided by what you actually produce under load. What the bands are cut from, and the reason the bands are personal while the number is absolute |
| Required WPM | The threat number: the pace, at perfect accuracy, that would clear everything winnable before it lands |
| Pace factor | The superlinear speed term in the XP formula |
| Pity | The counter that guarantees an Elite drop within six rolls |
| Power | One number summarising a loadout, compared against a sector's recommendation |
| Profile | A vulnerability sheet: how an encounter resists splash, pierce and mistakes |
| Reveal | Seconds of extra legibility bought by gear, drawn as a strip above the field |
| Sector | A destination with its own enemies and loot weighting, unlocked by account level |
| Correction | Backspace removes mistyped characters before correct letters. Ordinary targets keep their prefix; marked Rewind targets can reset at most twice |
| Challenge tier | One of 16 fixed typing workloads, 8 to 200 nominal WPM; separate from a vocabulary tier |
| Vocabulary tier | A word pool; current solo Starfall derives it from selected nominal challenge pace and wave depth |
| Tracer | A weapon's projectile as drawn: one of eight shape families with its own motion, so the equipped weapon is nameable from the corner of your eye |
| Transmission | The prose a boss broadcasts, which is what you type to strip a phase |
| The Zone | The flow state, lit by a long clean streak; feedback and contract progress in Starfall, without score or XP multipliers |
21. Constants appendix#
Every tuning number, with the file that owns it. If a value here disagrees with the code, the code is right.
Starfall performance, js/games/enemy-performance.js
| Constant | Value |
|---|---|
| PERFORMANCE_VERSION | performance-v1 |
| PERFORMANCE_REFERENCE_WPM | 40 |
| PERFORMANCE_MAX_WPM | 260 |
| PERFORMANCE_MIN_MS | 1,000 |
| PERFORMANCE_SCORE_PER_XP | 10 |
| Target reward | unique characters / 5 * capped unique-output WPM / 40 * reward accuracy squared |
Fixed challenges and boss mechanics
| Constant | Value |
|---|---|
| CHALLENGE_VERSION | challenge-1 |
| CHALLENGE_TIERS | 16, nominal 8 / 12 / 18 / 25 / 32 / 40 / 50 / 60 / 72 / 85 / 100 / 115 / 130 / 150 / 175 / 200 WPM |
| Boss typing target | round(nominal * 1.10) |
| BOSS_DAMAGE_CAP | 1.35 per original typed core character |
| MAX_MISTAKE_ADDS / MAX_TARGET_RESETS | 2 / 2 per target |
| WAVE_RULES_VERSION / WAVE_RECORD_CAP | performance-v1 / 600 |
Progression, js/xp.js
| Constant | Value |
|---|---|
MAX_LEVEL | 100 |
| level cost | round(60 + 40 * level^1.28) |
REF_WPM | 40 |
PACE_EXP | 1.35 |
| pace clamp | 0.35 to 8 |
| accuracy factor | (acc/100)^2 |
PLACE_CAP | 42 |
| per-game rank cost | round(120 + 70 * rank^1.15), cap 50 |
| Starfall scrap | Original carried-work kill value plus objective clears, scaled by salvage bonuses; no WPM or accuracy factor (section 4.3) |
The run funnel, js/run.js
| Constant | Value |
|---|---|
MAX_PLAUSIBLE_WPM | 260 |
MIN_RUN_MS | 2,000 |
| gambit multiplier clamp | 1 to 4 |
| loot multiplier clamp | 0.5 to 6 |
The arcade, js/games/starfall.js
| Constant | Value |
|---|---|
STEP / MAX_FRAME | 1/60 s / 0.25 s |
VIEW_TOP / REVEAL_TOP / BASE_Y | -0.04 / 0.12 / 0.93 |
SPEED_BASE | 0.024 heights per second, the planner's starting descent |
JAM_MS / JAM_RESET / HEAT_PER_MISS | 1,150 ms / 45% / 13 |
BREATHER_MS / COUNTDOWN_MS | 4,200 ms / 2,200 ms |
COMBO_STEP / COMBO_CAP | 10 / 4x |
ZONE_WINDOW | 40 keystrokes |
SCORE_CHAR / SCORE_KILL / SCORE_PER_LETTER / SCORE_CLEAN | 2 / 12 / 6 / 30 |
SCORE_PHASE / SCORE_MINI / SCORE_BOSS / SCORE_WAVE | 400 / 600 / 1,500 / 100 per wave |
SLOW_MS / FOCUS_MS / EMP_MS | 8,000 / 10,000 / 6,000 ms |
SLOW_CAP_MS / FOCUS_CAP_MS / EMP_CAP_MS / SHIELD_CAP | 16,000 / 20,000 / 10,000 ms / 60 |
ROCK_CONTACT_DAMAGE | 10, and only on real contact with the hull |
HULL_HALF_W / HULL_HALF_H | 0.085 / 0.045 playfield units, shared by the collision test |
BOSS_INTRO_MS / PHASE_HANDOFF_MS | 1,000 ms protected entrance / 1,200 ms handoff (solo also waits for a 220 ms input gap) |
MAX_PARTICLES | 260 |
PROGRESS_MS | 250 ms (how often a versus room hears from us) |
| Start wave | An UNLOCKED checkpoint (1 + n*K, after that block's boss was defeated), without a wave-depth cap. Above wave 1 the run is a checkpoint run. Selection, reload, launch and retry use the same earned clear record |
TRACER_CAP | 22 tracers in flight, oldest stolen |
| Resume countdown | 1,800 ms, plus a 120 ms input guard after GO |
| Deadline tiers | 4 s, 2.5 s, 1.5 s to arrival |
The difficulty contract, js/games/typing-effort.js
| Constant | Value |
|---|---|
SECONDS_PER_WORD_CHAR | 12 (60 seconds over 5 characters to the word) |
| Content rates | prose 1.00, words 0.95, glyph 0.80, code 0.72, random 0.55, drill 0.52 |
| Presentation rates | paragraph 1.00, line 1.00, stack 1.00, token 0.95, mote 0.95, chunk 0.95 |
| Presentation acquisitions | paragraph 0 s, line 0.15 s, stack 0.25 s, token 0.35 s, mote 0.35 s, chunk 0.40 s |
| Tier modifiers | punct 0.90, numbers 0.88, symbols 0.85, code 0.85, tech 0.90, caps 0.97 |
HIDDEN_REVEAL_S | 0.45 s, on the acquisition only |
| Rate clamp | 0.20 to 1.10. A rate outside it is a bug, not a slower typist |
The wave planner, js/games/wave-plan.js
| Constant | Value |
|---|---|
BLOCK_ENVELOPE / BOSS_ENVELOPE | 0.55, 0.62, 0.68, 0.76 / 0.80 of the baseline |
DIFFICULTY_PRESSURE | cadet 0.78, pilot 1.00, ace 1.22, nightmare 1.42 |
BLOCK_GROWTH | +2% a cleared block, capped by the sector ceiling |
| Sector ceilings | home 0.92, belt 1.05, deep 1.15, rift 1.30 |
windowS | clamp(30 + (wave - 1) * 1.3, 30, 72) seconds |
VISIBLE_SPAN | 2.8 gaps, capped by the sector's concurrency limit |
PERCEPTION_S / PERCEPTION_MULT | 1.5 s / 1.3x, the floor on a craft's visible lifetime |
| Home policy | 3 mandatory targets at once, 1 long phrase, no splitters, no carrier swarm |
| Home opportunities | 1 at a time, 2 a wave, one per 24 s of active combat, from wave 2 |
| Home deferrals | blackout and meteor before wave 11, blitz before wave 6 |
| Repair | delay the tail, then drop the most expensive PENDING craft, at most 6 times, then a plain fallback wave |
Combat feedback, js/games/combat-feedback.js
| Constant | Value |
|---|---|
LANDMARKS | +0, +10, +20, +30, +40, +50, +60 percent, ABSOLUTE |
COMBO_DMG_CAP | 0.60 (js/gear.js) |
| Audio pitch range | Smooth pentatonic contour over 24 semitones; charge 220 to 880 Hz, bonus voice 165 to 660 Hz |
Encounter settlement, js/encounter-rewards.js
| Constant | Value |
|---|---|
PROGRESS_VERSION | 1 |
RECEIPT_CAP | 240 receipts, oldest pruned, never the current run's |
DIFF_ORDER | cadet, pilot, ace, nightmare. A clear unlocks the same checkpoint at every EASIER difficulty |
The threat meter, js/games/threat.js
| Constant | Value |
|---|---|
floorS | 2.5 s |
capCps | 16.67 characters a second (200 WPM) |
lostEnter / lostExit | 1.10 / 0.85 |
spawnHorizonS | 6 s |
tauUpS / tauDownS | 0.35 s / 0.9 s |
capacityTauS | 20 s, load gated |
capacityClampWPM | 15 to 200 |
capacityWarmupChars | 50 |
bandsUp | 0.35 / 0.7 / 1.0 / 1.4 |
| band dwell up / down | 0.25 s / 1.2 s, at 0.92 of the boundary |
metricEveryTicks | 6 (10 Hz) |
displayCapWpm | 240, shown as 240+ |
| display quantisation | to 1 under 120 WPM, to 5 above; 2 WPM deadband, 400 ms hold |
| tier reference WPM | 30 / 50 / 75 / 90 by difficulty |
| Versus capacity | fixed 60 WPM |
The flight recorder, js/games/eventlog.js
| Constant | Value |
|---|---|
| Sim ring / view ring | 65,536 / 4,096 records |
| Aggregate series | 1 Hz, 1,800 samples, folded in place after that |
| Record | tick, type and provenance, three integers and a float. No strings |
| Timeline lanes | 6, Okabe-Ito, with lane position co-signalling |
| Coalescing window | 15 ticks (a quarter second) |
Encounters, js/games/events.js
| Constant | Value |
|---|---|
BOSS_EVERY | 5 waves (4 in the Rift) |
CHARS_PER_SHOT | 6 |
| encounter cooldown | 3 waves |
| encounter chance | clamp(0.22 + wave * 0.02, 0.22, 0.55) |
| mini-bosses appear | wave 6 |
| lap scaling | damage +22%, scrap +35%, escort +1 per lap |
| band caps | short under 30 WPM, medium under 55 WPM |
Gear, js/gear.js
| Constant | Value |
|---|---|
MAX_UPGRADE / UPGRADE_STEP | +5 / 14% per level |
SWAP_MS / SWAP_DAMAGE | 600 ms / 0.5 |
SECOND_HARDPOINT | Heat capacity -10; legacy score x0.94 is ignored by current Starfall rewards |
COMBO_DMG_CAP | +60% |
SKILL_POWER_PER_WPM | 1.2 |
Salvage, js/loot.js
| Constant | Value |
|---|---|
PITY_AT | 6 rolls |
| duplicate conversion | 20% to 40% of list price by rarity |
| first kill floor | Rare |
The meta layer, js/meta.js
| Constant | Value |
|---|---|
INSURANCE_EVERY / INSURANCE_CAP | 5 days / 3 tokens |
COMEBACK_AFTER_DAYS | 5 |
ZONE.enterCombo / enterAcc / graceMisses | 20 / 96% / 1 |
ZONE.scoreMult / xpPerSec | Legacy 1.25x / 3.2; current Starfall applies neither |
ASCEND_AT / ASCEND_BONUS | level 100 / +6% each |
The Academy, js/academy.js
| Constant | Value |
|---|---|
CERT.keySamples / keyAcc / groupAcc / reps | 20 / 90% / 95% / 1 |
| Groups / stages | 21 / 4 |
The skill model, js/skill.js
| Constant | Value |
|---|---|
MIN_KEY_MS / MAX_KEY_MS | 40 / 3,000 ms |
KEY_INERTIA / KEY_ALPHA_MIN | 0.25 / 0.1 |
KEY_KEEP / BIGRAM_KEEP | 140 / 400 |
SKILL_ALPHA_MIN / RUN_FULL_MS | 0.12 / 30,000 ms |
CLEAR_ACC / STAR_ACC_3 | 92% / 96% |
STAR_STEPS | 1.05x, 1.12x, 1.22x of your own baseline |
BASE_UP / BASE_DOWN / MIN_BASELINE | 0.35 / 0.06 / 8 WPM |
MIN_KEY_SAMPLE / OUTGROWN | 12 presses / 1.3x |
Versus, js/versus.js
| Constant | Value |
|---|---|
CODE_LEN / MAX_SEATS | 5 / 6 |
COUNTDOWN_MS / STARFALL_MS | 5,000 / 90,000 ms |
SPRINT_CHARS / SPRINT_CAP_MS | 340 / 210,000 ms |
HB_MS / AWAY_MS / HOST_GONE_MS | 15,000 / 46,000 / 90,000 ms |
WAVE_MS (race) / START_HP | 11,000 ms / 100 |
Presentation, js/art.js, js/music.js and js/state.js
| Constant | Value |
|---|---|
| Music crossfade | 900 ms |
| Music tracks | ops, hangar, combat, boss |
| Music default | on, at volume 0.5 |
| Sprite fallback | spriteOk(), with the hand-drawn shape behind every call |
State, js/state.js
| Constant | Value |
|---|---|
STORE_KEY | typingQuiz.v1 |
HISTORY_CAP / KEY_CAP / BIGRAM_CAP | 200 / 140 / 400 |
TYPING_BACKEND_URL | the shared worker, or empty for fully local |
Home pacing and equipment progression, September 2026#
Home now sends a continuous sequence of actual priced typing tasks. It rebuilds the arrival schedule after meaning-preserving collision repairs, so safety adjustments do not introduce long empty stretches. Target acquisition is priced once, at a fixed duration. The live target ceiling grows with the requested pace; optional salvage never changes the arrival schedule. Weapons can create breathing room by clearing work early. Transitions remain protected.
Ordinary ships now carry one to eight words, with 1–3-word scouts mixed with 4–8-word larger ships. Faster baselines add more arrivals through a wave rather than packing its work into three enormous ships. Home allows up to ceil(target WPM / 10) simultaneous hostiles, clamped to 3–18, with at most six longer ships. Arrival cadence comes from priced typing work. Ordinary enemies use independent attack clocks and staggered firing stations ahead of the player; see Enemy approaches, attacks and withdrawal. A pending arrival never shortens an existing ship’s attack deadline. Runtime admission also checks the live field: partially cleared ships reserve every remaining word initial, preventing an ambiguous target choice.
Current solo Home uses the fixed numbered challenge contract in section 7.2a: ordinary stages 0.88, 0.92, 0.96 and 1.00 of nominal pace, with formation factors, and bosses at 1.10 of nominal. Personal baseline, combat difficulty and depth do not multiply that typing workload. Mixed Patrol and Heavy Convoy use 1.00, Scout Flights 0.90, and Relay Pairs 0.94. The pre-flight workload band includes event-driven formation variants. Equipment changes the separate survival estimate.
Boss attack cadence uses the same pressure function as the briefing, frozen at entry. No clock ends the fight. Faster typing and suitable weapons shorten exposure; defensive equipment absorbs more volleys.
Earning and spending#
| Difficulty | Legacy score / training pace XP only | Scrap difficulty factor | Boss core bonus |
|---|---|---|---|
| Cadet | 0.8x | 0.85x | 0 |
| Pilot | 1x | 1x | 0 |
| Ace | 1.4x | 1.4x | 0 |
| Nightmare | 2x | 1.9x | 1 |
Current Starfall score and XP always use the per-target performance formula, with no difficulty multiplier. Its combat scrap uses only eligible kills/objectives and salvage bonuses, never measured WPM or accuracy. Training and legacy formats retain their previous speed-based salvage factor. The hand-entered baseline is never an earning input. Boss loot uses difficulty, not a claimed typing speed. Harder encounters weight Elite and Legendary cosmetics more heavily. Receipts remain idempotent; an escaped boss grants nothing and a completed encounter is never paid twice at run end.
First upgrades cost roughly 250 to 525 scrap, so a beginner can choose protection, recovery or a first weapon without a long shopping grind. Specialist builds are the next investment. Advanced items cost more; masterworks cost 12 to 21 boss cores. Clearing more eligible enemies earns more Starfall scrap. Faster accurate typing earns more performance score and XP; it adds no scrap factor. Existing ownership and upgrade levels are retained.
Upgrades keep their five-step cap and increase item strengths and weaknesses together. Their scrap cost is base * (0.35 + 0.30 * currentLevel^2), with two cores for +4 and four for +5. Buying another role competes with perfecting one item. Statless starter and empty items cannot consume upgrade currency.
Choosing equipment#
Loadout, Vendors and Cosmetics remain reachable in the Armory. Vendors have compact shop selectors, then search, equipment type, availability and sorting controls. Functional equipment is the default; cosmetics have an explicit filter. Vendor cards show all changed build effects in a fixed stat order, with gains and tradeoffs labelled in text. Descriptions and firing explanations remain optional. The comparison opens the same compact side-by-side stat panels used by the inventory.
The slot picker keeps the current item visible, compares the selected mount, and offers Buy & equip. Buying from a vendor adds to inventory; Choose mount & equip opens the picker. This prevents a purchase from silently replacing a second weapon or attachment. The Salvager offers three progression-relevant gear choices, an aspirational choice where available, and a fifth discovery. The shelf is stored for the day, so purchases cannot reroll it.
Sector progression#
Home teaches steady acquisition, short stacks, one readable sentence at a time and deliberate use of stored shots. It remains answerable without area weapons at the selected pace. The Belt adds charging sentries, independent boss batteries and a finite reactor breach, making target priority useful. Deep Space combines longer reading with dangerous specialist encounters, rewarding recovery, cooling and armour penetration. The Rift combines more simultaneous work and mixed encounter profiles, making complementary weapon mounts useful. Deep Space and the Rift retain their authored encounter rules; future passes should test each new mechanic independently before combining them.
Arcade world guide#
The following guide is generated from STORY_BIBLE.md and describes the arcade setting.
Starfall Keys: arcade world guide#
Home Sector: Beacon Nine#
Beacon Nine is a working orbital shipyard: cyan docking lights, warm repair bays, slow salvage tugs and a bright planetary horizon. The player pilots its defense ship against the Scrap Armada, a rogue machine fleet that keeps arriving without clearance. The objective is straightforward: clear the sky and bring the ship home.
The mood is energetic space arcade action. Metal catches the sunlight, engine trails cross the dark, antenna arrays glow, and the hangar crew appreciates a spectacular finish. Radio messages are short, useful and occasionally cheeky. The machines can boast about their hardware. Flight Control can joke about the scrap bill. Neither needs a tragic backstory to make a good boss entrance.
The fifty-wave operation#
Every five-wave group has a distinct visual setting and objective:
| Waves | Operation | Setting and objective |
|---|---|---|
| 1–5 | Launch Ring | Clear the first attack from the bright docking lanes. |
| 6–10 | Chrome Swarm | Break the linked drone formations around the outer docks. |
| 11–15 | Heavy Metal | Stop the armored push through the shipyard. |
| 16–20 | Ghost Signal | Clear the interference surrounding the beacon. |
| 21–25 | Solar Run | Hold the sunward side of the station through the glare. |
| 26–30 | Salvage Alley | Open the scrap route for the collection crews. |
| 31–35 | Ion Weather | Fight through blue clouds and bright engine trails. |
| 36–40 | Night Shift | Secure the dark side of the orbital ring. |
| 41–45 | Redline Orbit | Break the final screen around the approach lanes. |
| 46–50 | Last Light | Finish the attack and return to the home beacon. |
These are briefing settings, not extra gameplay modifiers. The installed wave plans, weather effects, enemy mechanics and rewards remain authoritative. Home ends after the wave-50 boss. Its review leads to the run summary.
Enemy identities#
- Drones: compact raiders, quick to arrive and quick to turn into scrap.
- Couriers: sleek hot deliveries with no docking permit.
- Cruisers: heavy weapon platforms with plenty of plating to scratch.
- Elites: polished command hardware worth a closer look in the salvage bay.
- The Sentinel: a gate siege platform with paired weapon banks.
- Hive Mind: a command array directing linked drone formations.
- Dreadnought: a heavily armored flagship built to break a defense line.
- The Oracle: a precision cipher engine with a taste for interference.
- Raider, Harvester and Warden: fast interception, aggressive salvage and armed checkpoint roles. Their names describe their jobs in the machine fleet.
Defeating a command ship adds its scan to the Operations briefing. The scans are flavor and a record of familiar hardware, not an additional reward system.
Beyond the home ring#
The Belt is a rough salvage route with rocks, sentries and rival batteries. Deep Field is a distant shipping lane with larger hulls and rich scrap. The Rift is the dangerous edge of the flight map, with frequent command ships and rare salvage. Their existing wave pacing and unlock rules do not change.
Writing and implementation rules#
Game fiction is its own setting. Do not import characters, events, quotations, chronology or source text from the separate Book reader or manuscript. Keep the reader, its chapter files, art and reading preferences isolated.
Use flight, machine, weapon, station and salvage imagery. Keep briefings short enough to read between waves. Flavor must not promise a mechanic that does not exist. Boss IDs and combat behavior stay stable when their descriptions change.
js/lore.js owns fifty ordered wave dispatches, ten operation briefings and command-ship flavor. js/lore-variety.js owns optional radio traffic. General typing-library material is independent practice content. Neither pool imports or adapts the manuscript. Preserve typed spelling, keyboard eligibility, seed determinism and the existing whole-sentence rules.
Wave formations, target practice and returning to play#
Mixed Patrol alternates short scouts and larger ships. Scout Flights concentrates on one to three words per ship. Heavy Convoy uses larger ships with short escorts. Relay Pairs shortens the gap within a compatible pair and adds that time after the pair. The rotation changes across boss blocks; scout and cargo events select their matching formation. The wave banner names the current formation, while the breather forecasts the next patrol and notes that events may change it.
The first introduction opens a directed game rehearsal: finish red, pause on the pending separator, switch to orb, and return directly with s. A second practice covers switching after Space. The player controls every explanatory pause; the demonstration does not advance a paragraph on a timer. Skip records that the introduction was offered. The Armory Ability Guide and the ↔ control replay it at any time. It changes no combat resources or rewards.
Directed ability scenes#
Charge, Target Relay and all five power-ups use ability-lesson.js. It presents the production Starfall canvas, HUD, ships, targeting, input rules and effects inside an isolated practice frame. Explanations are short, reader-paced steps. A spotlight and adjacent label identify the relevant word, meter or slot. Show me plays the action, then freezes on its result. Back reconstructs the earlier checkpoint, Replay restarts the current step, Pause stops an action, and Skip or Escape always exits. Charge and Relay include real-input practice with no failure timer and a click-operated demonstration alternative.
The frame uses fresh, memory-only state, no recorder, no cloud boot, and no wave or encounter settlement. Its authored clock advances only during a demo or practice feedback. The actual run remains frozen. It inherits the player's art and motion settings. Armory → Ability Guide is the permanent replay door, including each discovered power-up. Unknown powers are not taught in earlier lessons. TUTORIAL_DESIGN.md records the design references and review criteria.
Every ordinary solo run saves its sector, difficulty and latest started wave immediately, independently of end-of-run rewards. Returning selects that wave’s earned block start: wave 19 selects Waves 16 to 20. A boss victory saves the next unlocked block immediately. Older saves without this position select their highest earned block in the selected sector and difficulty. Intentionally replaying an earlier block becomes the new last-played position. The position grants no checkpoint access; Versus and developer wave overrides do not change it.
Enemy approaches, attacks and withdrawal#
Hostile ships remain ahead of the player. Regular enemies approach staggered firing stations, no lower than 62% of field height. Raiders (one to three words) approach, aim, fire one 3–5 damage shot and retreat upward. Gunships fire three 3-damage shots; siege ships fire four. Heavy ships fire during their approach and shorten the intervals between shots, with a minimum readable warning. They withdraw after their last shot. Leaving the field causes no breach damage. Optional salvage retains its physical collision behavior.
enemy-attacks.js prices the original text at the mission's target WPM. Attack timing is independent of screen height and the old crossing-speed cap. Every ship displays its role, FIRE IN countdown, and filling warning bar; aiming lines and corners reinforce the warning without relying on color or animation. Kill before firing to prevent the shot. A launched projectile travels for 0.65 seconds and still hits after the firing ship dies. Shield and armour apply on impact. Missing the first shot does not make a heavy target worthless: killing it prevents subsequent shots.
Slow and fitted field slowing affect approach, aim and retreat together. Reveal delays the opening attack. Pauses and protected reading/tutorial states freeze the entire simulation. The threat meter and untargeted charge selection use the next firing deadline; harmless withdrawing ships leave the demand sum. The survival estimate uses the same finite attack schedules and allows chip damage, rather than assuming a damage-free trip across the screen.
Higher typing demand creates more overlapping arrivals and a larger live target ceiling, rather than accelerating the normal enemies or making giant paragraphs. scheduledOverlap describes unanswered arrival slots; liveTargetLimit describes actual admission capacity. Pending ships wait for capacity or distinct word initials without changing visible text or speeding up enemies. Beginner fields stay smaller.
Detailed run analysis and recordings#
Open Progress → Run analysis to review typing recordings, or expand Typing analysis and run timeline on a Starfall or Flight Check result. Recording starts with this feature; older History rows have only their original summary. The reward History and leaderboard rules are unchanged. Abandoned sessions earn no run rewards, but their input is retained in this separate analysis archive, labelled abandoned or interrupted.
Progress opens with typing measures and run analysis before currency, contracts and rank rewards. Recent WPM and accuracy are weighted averages across modes; they are labelled as observations rather than proof of skill improvement. The accuracy practice and 60-second measurement buttons lead to Training and the Flight Check directly from this summary.
Within a session group, the recent comparison uses median WPM and median accuracy from the latest three completed recordings with at least 30 seconds of measured typing. Once six exist, it compares the previous three without overlapping the windows. Short and unfinished sessions remain reviewable. The screen shows both changes and flags a speed increase paired with an accuracy decrease greater than half a percentage point. These observations are not a skill rating: text, run length, gear and fatigue can still vary.
Your typing over time defaults to all saved runs, in chronological order. It joins synced profile history with device-local recordings by recording ID, so an older summary-only run is charted and a run stored in both places appears once. The run table identifies summary-only entries; only actual recordings offer detailed review. A game filter and the narrower comparable groups are optional. The broad overview labels differences in settings and historical WPM definitions and does not combine them into a median skill comparison. If detailed storage is unavailable, profile history still supplies the graphs.
Immunity at the typing text: charge-immune enemies and boss passages carry a double rule and a TYPE ONLY label. Their focused input explains that charge shots and Nukes are blocked. Asteroids carry a single rule and NO NUKE, since charge shots still work on them. Color reinforces these marks but is not their only signal. Error cells, the caret and deadline underline retain their meaning. Ordinary targets being typed are temporarily skipped by Nukes, not marked immune.
The report includes correct WPM, accuracy, mistakes, corrections, a timeline with 5, 10 or 30 second buckets, and individual enemy and boss results. Select combat, enemies, bosses, mini-bosses, asteroids, power-ups, Repair practice, paragraph typing or all activity. Untyped targets show N/A, not a fabricated speed. Completed-run trends group the same game, mode, sector, difficulty, threat tier, starting checkpoint, baseline source, manual baseline and metric version. Placement measurements are separate from ordinary sprints. Pattern evidence also uses unfinished sessions in the same group.
Run results, selected-activity statistics and charts appear before calculation details. Expand How these numbers are measured for the full timing and reward methodology.
Correct WPM is correct accepted characters divided by five and engaged minutes. Retypes remain accepted attempts, matching the game. Accuracy is correct attempts divided by all scored attempts in the selected activity; backspace does not remove mistakes. Combat time excludes pauses, protected cards, asteroids, power-ups and target acquisition gaps. The existing estimated opening-character interval is recorded separately as synthetic time, so it can be included or removed by later analysis. Paragraph timing starts on its first key. Raw event timestamps are not clamped to the skill model's latency limits.
Timeline speed instead divides correct characters by five and the active window's minutes. Whole keystrokes must not be divided by a tiny fragment of engaged time left over at a bucket boundary: that previously produced false spikes of thousands of WPM. This window rate includes target-selection gaps, excludes paused time and ignores synthetic opening estimates. Run and target WPM retain their engaged-time definition. Final partial windows use their actual duration; windows under one second show no speed estimate. Their exact counts and accuracy remain available, and an event exactly at the end of the run belongs to the final window. Inactive windows are gaps in the speed line. Point tooltips and keyboard-focus labels include attempt counts, window length and engaged seconds. The timeline table also identifies targets. These rules are applied when reading existing recordings; raw events are never discarded or capped, and completed-run trends are unchanged.
Practice suggestions and strengths are evidence-based counts, not diagnoses. A pattern needs five attempts; a practice suggestion also needs two mistakes. Keys, preceding-key pairs, following-key pairs and three-character contexts are kept separately. A three-character context measures its middle key, and a following-key pair measures its first key. Whitespace and punctuation are retained. Follow-on characters in an existing error buffer count against accuracy but do not blame the expected key as a new independent mistake.
Recording format and storage#
js/run-analytics.js owns schema version 1 and metric version correct-chars-5-engaged-v1. Each recording contains a random run ID, start/end date, owner storage slot, run status, game/mode, relevant configuration, and an ordered event stream with both active and wall milliseconds. Starfall also records seed, balance version, starting wave, baseline, loadout, game stats, target spawn text and identity, word completions, target outcomes, encounter phases, state changes, pauses, weapon switches, discharges, flares and incoming damage. Each scored key stores actual and expected characters, text position, preceding/following text, target and prompt IDs, scope, raw key-to-key latency, blocked-error status and available keyboard/input-event metadata. Corrections, physical key releases and unscored gameplay controls are separate events. Input belongs only to the game, never account fields or other open dialogs. Prompts are stored once and referenced by ID; the stream preserves order even when a virtual keyboard delivers a batch at one timestamp. Virtual input does not promise physical key-release data that the browser never supplies.
js/run-archive.js writes IndexedDB database btq.run-archive.v1. Its runs store holds headers, summaries and contextual tallies indexed by owner slot; chunks holds event batches keyed by run ID and sequence. A header and its new batch commit in one transaction. Failed writes retain the pending events for retry and show a warning. Checkpoints are queued within two seconds of an event, with additional saves on pause, pagehide and completion. A browser crash can lose events since the last committed checkpoint; the surviving record remains labelled interrupted, never completed. Live data can still be exported from the end report when durable storage fails.
Event batches and cached pattern tallies use standard gzip compression through the browser's native compression streams, with plain JSON Blobs as the fallback when compression is unavailable or would make a payload larger. The public event schema and all available fields, timestamps and ordering are preserved; unavailable undefined properties are omitted, as in the JSON export. No samples are discarded and no clock precision is reduced. Compression finishes before opening the IndexedDB transaction. At run end, checkpoint batches are replaced atomically by one compressed batch so repeated context can compress across the whole run. The old checkpoints remain intact if that transaction fails. Repeated completion flushes do not rewrite the finished run.
The reader accepts both the original object-array chunks and encoded Blobs. Older completed or abandoned recordings are compacted when reviewed, only if their saved header has not changed or been deleted during conversion. Failed conversion preserves the original. Active/interrupted checkpoints remain independent batches, so recovery does not depend on a successful final merge.
For a reproducible size comparison, run node tools/check-archive-codec.mjs. Its varied-text fixture records 4,000 correct characters plus 119 mistakes and corrections with variable timing. Approximately 3.15 MB of uncompressed events becomes 325 KB including the compressed pattern cache and header, before IndexedDB's own bookkeeping. This is about a 90% reduction in payload size; actual runs vary with timing, text and encounters. This measures serialized storage payload, not the browser's exact disk allocation or live memory use.
Detailed recordings stay on this browser and are separated by guest/account storage slot. They are not included in cloud sync or the existing profile backup, whose compact History remains capped at 200. The detailed archive has no automatic run-count eviction. Browser storage clearing or quota limits can affect it. Export one run or the whole archive as JSON before clearing browser data. Export compressed downloads the same JSON in a standard .json.gz file where browser support is available. The ordinary JSON export stays readable and uncompressed. Exports omit the internal account-slot identifier. Delete a recording from its review when no longer needed. Clear all data also clears this slot's recordings. An exported recording is analysis data, never a reward receipt or a leaderboard submission.
analyseRecording(recording, { scope, bucketMs }) can recompute reports from events without changing game state. Run node tools/check-analytics.mjs for timing, scope and contextual evidence invariants, and python tools/check-analytics-browser.py for actual typing, reload durability, account isolation, export and responsive report checks.
Shield projectors and interception#
The shield phase uses two visibly linked projectors. Their word workload targets roughly 30 seconds at a fixed design pace (clamp(nominalWpm * 0.7, 8, 140)), including eight optional interception keys. Faster typing clears them sooner. The core opens for 15 seconds, with fixed health of round(designPace * 3.2) and 4% repair on closing. The reference rhythm is three openings with starter damage, fewer for strong typing and more under pressure; this is a balance target, not a forced loop count. Combat continues through each attempt, so slow clears face more attacks. Completed layers stay removed when the player switches targets at a word boundary. A projector keeps its silhouette size while its armor ring loses segments. All ordinary enemies also keep their size as remaining collision damage falls.
The closing countdown follows the passage cursor for the final three seconds. An already-started word can finish; a 500 ms protected beat keeps the outgoing passage visible, then replacement targets appear with input immediately enabled. Boss phase changes use the same outgoing beat when unread text remains. Completed generators and fully completed passages do not add a pause. Multiword enemies show their remaining keystrokes directly under the current word, independent of the read-ahead queue.
Up to two interceptors launch per relay phase, with only one live at a time. Each shows a six-second firing countdown; typing or discharging into it before launch prevents the shot. Its word never changes when the timer expires. These are bounded defensive targets with no repeat-farming rewards. The full-width battlefield keeps typing labels unobstructed; shield links, hit ripples and armor fragments communicate state without expanded instructions.
Threat selection uses one keyboard-accessible scale. Existing tiers1 through16 retain their authored work; tiers17 through19 extend it. The loadout survival range remains an estimate at95%accuracy without charges, not a measured skill requirement. Tests of steady cadence and bursty accelerated play can differ, so survival estimates must not be presented as validated human thresholds.
Belt combat identity#
The Belt uses industrial weapons with explicit firing intent. Selected planned drones become Sentries and selected cruisers become Siege drills. They descend normally, start charging only after entering view, and can fire twice. Each shot moves 25% of the original breach damage into a visible projectile; the remainder is paid on arrival. Destroying one prevents future shots but does not erase shots already travelling. Firing never replaces its reading or discards a typed prefix. First-shot timing scales with the authored reading workload; Slow also slows charging. These targets add priority decisions without adding hidden text or an unlimited damage source.
Every Belt boss opens with two independent batteries: a light gun and a slower heavy gun. Typing out a battery permanently disables it for that phase. Both must be disabled to advance. Their visible countdowns and damage appear beside the remaining-work counter under the current word. The ordinary boss attack pattern is suspended while the batteries provide the attack pressure. Repair does not pause them. The four combat designations are The Foreman, Scrap Choir, The Anvil and Flux Engine; they share a new industrial hull asset with canvas weapon effects. Encounter IDs and progression receipts retain their existing identity.
Scrap Choir's final reactor phase has two coolant clamps. Each stays broken after completion. Once both are gone the core remains exposed until defeated, with normal boss volleys still active. It has no timed reclosing, regenerated clamp cycle or extra relay interceptors. Other relay encounters retain their existing timing. This gives the foundry a sustained accuracy finish instead of another shield endurance loop.
The loadout forecast shares sentry damage clocks and battery timing with combat, includes projectile travel and attacks already in flight, and models the finite reactor exposure. Its battery policy finishes the light gun first; it is an estimate, not an optimal player or a measured human skill threshold. Automated replay findings must report actual measured WPM separately from input cadence.
On short desktop windows, boss prose uses a bounded reading viewport with whole-line caret following, preserving upcoming text without pushing correction controls and reserves below the game. Character nodes and passage content remain stable. Countdown headroom, a compact correction row and the existing accuracy/source information stay visible. Repair retains its own timed prompt and scrolling behavior. Larger desktop windows retain the expanded passage presentation. The masthead scrolls normally while a desktop game is mounted so it does not cover the combat HUD.
Ideas reading library#
Arcade enemy text now comes from the separate ideas library rather than the manuscript. The collection contains verified public-domain quotations, original research summaries, original short phrases, complete sentences, longer passages, and whole vocabulary words. Original writing is never presented as an author's quotation. Source metadata preserves author, work, translator, URL, locator, rights basis, verification notes, and verbatim text. Gameplay normalizes typographic punctuation for a standard keyboard; incomplete quotation excerpts remain catalog entries and are excluded from complete-reading selection.
The arcade footer links to Explore the reading library. Its browser filters by topic, format, writing type and search text, and exports the collection with source metadata. Gameplay draws from the whole eligible collection, with a gentle thematic preference: learning and kindness in Home, practice and achievement in the Belt, reflection and discovery in Deep Field, and courage and resilience in the Rift. Fresh readings take priority over themes. Media and source balancing keeps smaller books, movie favorites, songs and poetry visible alongside large anthologies. The existing training curriculum and archived manuscript remain available separately.
Generator sides are physical positions, not hand restrictions. Each carries a complete library reading; the selection varies within an exact character-cost pool. Whole original sentences on the same topic may be paired, with both entry IDs retained. Quotations are never spliced together. The remaining-work number sits below the active word, and only the final word destroys the generator. Small swarm targets and salvage rocks now carry whole words and are priced as word targets rather than glyph or random-key tasks. Boss passages select whole entries with repeat avoidance and a compact author or original-writing label.
Mini bosses and boss phases retain the speed-based short, medium and long reading bands. Short verses and brief lyric excerpts can enter shorter bands; longer stanzas and paragraphs support sustained phases. Openings remember earlier planned readings across encounters. Extra reissues use a separate live history so errors and combat timing cannot change future shared-seed waves. Generator and battery readings also include verses, retain exact character costs and distinct target initials, and rotate sources through returning shield cycles.