In-Mission Tier Progression
An arcade run is a fixed sequence of timed levels. Each level lasts 240 seconds (4 minutes) on the missionTimer; when the timer hits zero the engine advances to the next level and applies a hard escalation step to every spawn-pressure dial. The level counter that drives this is game._currentLevel, exported at run-end as MissionResult.progression.tierReached.
This is the in-mission tier counter — the 4-minute escalation step inside a single run. It is distinct from ship star tier (a meta-progression value on the player’s collection ships, persisted across runs).
The 4-minute step
DEFAULT_RUN.node.timerSeconds = 240. When game.missionElapsed reaches that, the engine enters phase = 'level_advance', a short banner fade plays (0.3s), then game._currentLevel += 1 and a fresh world is generated from a new seed. The Sig bus fires tier_advance so effect-system subscribers (audio stings, post-fx cues, ambient swaps) can react.
Escalation step (per tier advance)
On every tier bump the engine multiplies game.worldKnobs in place — the spawn director reads these knobs every frame so the ramp is instant and permanent for the new level:
| Dial | Multiplier per advance |
|---|---|
enemyHpMult | ×2.25 |
enemyDamageMult | ×2.15 |
enemySpeedMult | ×1.14 |
enemyCountMult | ×1.65 |
_attackSpeedMult | ×1.50 |
enemyDifficultyLevel is decayed by 75% on each advance so the per-level stat bonus inside the spawner resets to ~25% of its previous value — the baseline ramp lives entirely in the world-knob multipliers above, while the spawner’s level-stat bonus restarts each tier.
Base multipliers (_baseSpeedMult, _baseDamageMult, _baseHpMult) are reset to 0 on advance — overtime inflation from the prior tier does not carry forward, the next tier scales from the freshly-multiplied base.
Level-kind sequence (the run shape)
Normal runs are 5 levels; Challenge runs are 10. Each slot’s LevelKind is resolved by resolveLevelKind(currentLevel, isChallenge) from data/level-progression.ts:
Normal: normal → normal → mini_boss → hard → boss
Challenge: normal × 2 → mini_boss → normal × 2 → mini_boss → hard × 2 → mini_boss → boss
normal/hardlevels are timed biome levels that end when the 4-minute timer expires (or overtime resolves).mini_boss/bossare sealed-arena levels — no biome, no hubs, no events. They end when the roster dies (game._bossLevelCleared), not on the timer.hardlevels apply the same per-tier escalation but pin the difficulty ramp at 1.0 and skip the basics-only window.
isFinalLevel() returns true at the last slot; on clear, the run flips to results instead of advancing.
Per-planet cap
A planet’s run sequence is its hard cap. Normal runs cap tierReached at 5; Challenge runs cap at 10. The engine clamps out-of-bounds level lookups to the final boss kind so dev/sandbox can push beyond, but production runs end at results when the final level clears.
Where tierReached is written
game._currentLevel is copied into missionResult.progression.tierReached at:
- death (
bridge.ts:3599) - snapshot (
bridge.ts:8475) - boss-clear / run-end (downstream of
progression.levelReachedwrites nearbridge.ts:3917)
Starts at 1 per createEmptyResult() in data/mission-result.ts:154.
What it drives downstream
- Final reward chunks (Credits).
data/economy.ts:53computesbaseAtTier(tier) = (200/3) × 2^(tier-1)then layers a kill bonus, event bonus, and level bonus — each one isbase × clamp(stat / (2 × par), 0, 1)whereparscales with tier (pK = 500 × tier,pE = 12 × tier). The four chunks (tierBase,killBonusCredits,eventBonusCredits,levelBonusCredits) sum tototaland drive the tween animation onRevealScreen. - Mod-grid drops.
services/mission-drops.tskeys drop count offtierReachedvs the stored per-planet PB:>= PB → 3 drops,PB − 1 → 2, otherwise1. First run with no PB gives 2. - Personal bests.
RevealScreencallssaveTierPB(nodeId, tierReached)after the reward animation; subsequent runs read this back viacomputeDropCountand the PB-badge logic onRunStatsScreen. - Run-history aggregates.
data/run-history.tsstorestierReachedper run, averages it across history, and surfacesbestTier = max(tierReached)to the profile. - Telemetry.
runProgressionServicewritestier_reached: result.progression.tierReachedto Supabase; the sampler tagsrun_endevents withfinalLevel: game._currentLevel. - Artifact tier ramp. Artifact box spawn intervals shorten with
_currentLevel:tierRamp = min(1, ARTIFACT_TIER_RAMP_BASE + (currentLevel - 1) × ARTIFACT_TIER_RAMP_PER_LEVEL).
Reset semantics on advance
When a tier advances the engine wipes essentially all transient world state: enemies, bullets, XP orbs, pickups, crates, debris, props, warnings, weapon boxes, artifact boxes, destructibles, terrain, structures, hubs, spokes, chunks, events, sigils, comets, patrols, regen stations, post-fx. The ship is recentered at (0, 0) with 3 seconds of invulnerability, Rapier is fully rebuilt (because boss/portal transitions can wedge the WASM RefCell), the world seed is randomized, biome geometry regenerates from WorldGenerator.generate() (skipped for sealed-arena kinds), and spawners + background reset.
Distinct from ship star tier
tierReached is a per-run counter and never persists onto a ship. Ship star tier is a separate value on the ship’s collection record, ranges 1–3, and drives meta-progression rewards via the mission reward tables and STAR_TIER_CURVE. Run output writes only to MissionResult, never to the ship.
Key code
data/mission-result.ts:54-72—progression.tierReachedfield definition.data/level-progression.ts—RUN_LEVEL_SEQUENCE,CHALLENGE_LEVEL_SEQUENCE,resolveLevelKind,isFinalLevel.data/run-config.ts:526—timerSeconds: 240.engine/bridge.ts:3642-3838— tier-advance block: counter bump,tier_advancesignal, world-knob escalation, world reset, Rapier rebuild, banner, timer reset.engine/bridge.ts:3599,:8475—tierReachedwrites to MissionResult.data/economy.ts:48-83—computeArcadeCreditsBreakdown(final reward chunks).services/mission-drops.ts:23-28—computeDropCount(tierReached, tierPB).screens/RevealScreen.tsx:369-395— readstierReached, saves per-planet PB.