dev-scenarios

PURPOSE

Registry of pre-configured RunDefinition builders for visual/balance testing. Each scenario is activated by a ?dev=<name> URL query param and produces a fully-formed run config tuned for a specific test case (elite sprite verification, telegraph rendering, damage curves, artifact behavior, terrain rendering, etc.). Enemies remain live (normal AI/movement) — this is a playground for shipping QA, not a static scene.

OWNS

  • DevOverrides interface — typed shape of ?weapon=, ?level=, ?ship=, ?kills=, ?tier= URL params.
  • parseDevOverrides() — reads window.location.search into a DevOverrides struct. Returns {} when window is undefined (SSR safe).
  • SCENARIOS: Map<string, ScenarioBuilder> — module-scope registry, populated by register(name, builder) calls at module load time.
  • register(name, builder) — private registration helper invoked once per scenario at the bottom of the file.
  • buildDevScenario(name) — public lookup. Returns a fully-built RunDefinition or null if the name is unregistered (logs console.warn with available names).
  • listScenarios() — returns all registered names (for dev console help text).
  • baseRun(overrides) — deep-clones DEFAULT_RUN, applies common dev overrides (durable ship, no fog, 10-min timer, events unlocked, weapon/ship/level overrides). Every scenario starts from this.
  • Scenario builders: sunrise-city, elite-showcase, telegraph-test, damage-test, speed-compare, horde-peak, flame-test, empty-canvas, weapon-showcase, pillar-test, pillar-only, plus artifact suite (art-echo-gen, art-fortress, art-droid, art-crate-bust, art-tbone, art-ram, art-reactive, art-healer, art-soul, art-force-field, art-personal-space).

READS FROM

  • ../data/run-configRunDefinition type and DEFAULT_RUN constant (cloned per scenario via JSON.parse(JSON.stringify(...))).
  • window.location.search — only on the browser; guarded by typeof window === 'undefined' check.

PUSHES TO

  • Returns RunDefinition objects to the caller (bridge / dev API). This file does not mutate state directly.
  • Uses (def.context as any).devAutoArtifacts and (def.context as any).supplyLevel escape-hatches — these are honored downstream by the bridge dev API / run-init code, not by anything in this file.
  • _devPreSeedKills (referenced in comment on horde-peak) is handled by the bridge dev API reading the ?kills= param separately — this file does not set it.

DOES NOT

  • Does not import or touch the actual game engine, stores, or rendering code.
  • Does not register URL listeners or auto-activate scenarios — entry-point code (bridge / dev API) calls buildDevScenario(name) explicitly.
  • Does not validate DEFAULT_RUN shape — relies on it being well-formed.
  • Does not mutate DEFAULT_RUN (always deep-clones).
  • Does not handle errors from unknown weapon/ship IDs — passes the string through to RunDefinition as-is.
  • Does not write to disk, network, or storage.

Signals

  • console.warn('[dev-playground] Unknown scenario: "${name}". Available: ...') on buildDevScenario miss.

Entry points

  • ?dev=<scenario-name> URL param (consumed by bridge / play-screen init code).
  • Optional URL params: ?weapon=<id>, ?level=<int>, ?ship=<id>, ?kills=<int>, ?tier=<int>.
  • Example URLs from file header: /games/starship-survivors/play?dev=elite-showcase, ?dev=telegraph-test&weapon=laser&level=15, ?dev=horde-peak&autoplay.

Pattern notes

  • Registry-at-module-load. All register(...) calls execute at import time; SCENARIOS is fully populated before any caller invokes buildDevScenario.
  • baseRun is the shared baseline. It enforces dev-friendly defaults: zero fog (darkOpacity = 0, baseRadius = 9999), durable ship (overridable per scenario — damage-test and flame-test reset HP/shield to realistic values), long timer, events unlocked. Scenarios then tweak worldKnobs, spawn, context, or ship.combatStats on top.
  • Deep clone via JSON round-trip. JSON.parse(JSON.stringify(DEFAULT_RUN)) — safe because DEFAULT_RUN is plain data with no functions, Dates, or Maps.
  • Quantity curves are flat-line for steady stress. Most non-progression scenarios use [{ time: 0, value: X }, { time: 9999, value: X }] to hold density constant across the test.
  • Artifact scenarios share a shape. Each art-* builder sets (def.context as any).devAutoArtifacts = [{ id, tier: ov.tier ?? 0 }] and def.node.artifactBoxCount = 0, then tunes world knobs / ship stats to exercise that artifact’s mechanic (e.g. art-fortress lowers HP to make DR visible; art-soul lowers enemy HP for fast ghost procs; art-tbone/art-ram boost player thrust for ramming).
  • as any escape hatches for devAutoArtifacts and supplyLevel indicate these are dev-only fields not in the canonical RunDefinition type — they are honored by downstream init code that knows to look for them.
  • planetId + biome paired. Biome-driven terrain scenarios (sunrise-city, pillar-test, pillar-only) set both def.context.planetId and def.node.biome — biome drives terrain generation, planet ID drives metadata. pillar-only uses planetId: 99 as any for an out-of-band non-player-accessible test world.
  • Numeric tuning lives in the scenario file, not in shared data. Each scenario hardcodes its own enemyCountMult, enemyDamageMult, spawnInterval, batchSize, etc. — these are dev-tool knobs, not game balance.

EXTRACT-CANDIDATE

  • Deep-clone-plain-data idiom. JSON.parse(JSON.stringify(x)) recurs whenever a config struct needs cloning before mutation. If a second consumer (test fixtures, snapshot tools) appears, hoist to a cloneRunDefinition() helper next to DEFAULT_RUN.
  • Flat-line quantity curve. [{ time: 0, value: X }, { time: 9999, value: X }] is repeated across elite-showcase, telegraph-test, speed-compare, horde-peak, empty-canvas, weapon-showcase. A flatCurve(value) helper could deduplicate, but the duplication is small and self-evident — only worth extracting if a third use pattern (ramp, pulse) appears alongside it and benefits from a shared API.
  • Artifact-scenario shape. All art-* builders repeat: set devAutoArtifacts, zero artifactBoxCount, tune knobs. An artifactScenario(id, tweaks) helper would compress the suite — but right now per-scenario tweaks vary enough (HP, thrust, shield, supplyLevel) that the shared shape is the only commonality. Reassess if the artifact list grows past ~15.
  • Durable-ship vs realistic-ship split. baseRun defaults to a durable ship; damage-test and flame-test reset to realistic values; art-fortress and art-healer pick their own. A realisticShip(def) or durableShip(def) toggle could clarify intent, but the override is currently a 3-line block per call site — not yet load-bearing duplication.