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:

DialMultiplier 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 / hard levels are timed biome levels that end when the 4-minute timer expires (or overtime resolves).
  • mini_boss / boss are sealed-arena levels — no biome, no hubs, no events. They end when the roster dies (game._bossLevelCleared), not on the timer.
  • hard levels 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.levelReached writes near bridge.ts:3917)

Starts at 1 per createEmptyResult() in data/mission-result.ts:154.

What it drives downstream

  • Final reward chunks (Credits). data/economy.ts:53 computes baseAtTier(tier) = (200/3) × 2^(tier-1) then layers a kill bonus, event bonus, and level bonus — each one is base × clamp(stat / (2 × par), 0, 1) where par scales with tier (pK = 500 × tier, pE = 12 × tier). The four chunks (tierBase, killBonusCredits, eventBonusCredits, levelBonusCredits) sum to total and drive the tween animation on RevealScreen.
  • Mod-grid drops. services/mission-drops.ts keys drop count off tierReached vs the stored per-planet PB: >= PB → 3 drops, PB − 1 → 2, otherwise 1. First run with no PB gives 2.
  • Personal bests. RevealScreen calls saveTierPB(nodeId, tierReached) after the reward animation; subsequent runs read this back via computeDropCount and the PB-badge logic on RunStatsScreen.
  • Run-history aggregates. data/run-history.ts stores tierReached per run, averages it across history, and surfaces bestTier = max(tierReached) to the profile.
  • Telemetry. runProgressionService writes tier_reached: result.progression.tierReached to Supabase; the sampler tags run_end events with finalLevel: 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-72progression.tierReached field definition.
  • data/level-progression.tsRUN_LEVEL_SEQUENCE, CHALLENGE_LEVEL_SEQUENCE, resolveLevelKind, isFinalLevel.
  • data/run-config.ts:526timerSeconds: 240.
  • engine/bridge.ts:3642-3838 — tier-advance block: counter bump, tier_advance signal, world-knob escalation, world reset, Rapier rebuild, banner, timer reset.
  • engine/bridge.ts:3599, :8475tierReached writes to MissionResult.
  • data/economy.ts:48-83computeArcadeCreditsBreakdown (final reward chunks).
  • services/mission-drops.ts:23-28computeDropCount(tierReached, tierPB).
  • screens/RevealScreen.tsx:369-395 — reads tierReached, saves per-planet PB.