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
DevOverridesinterface — typed shape of?weapon=,?level=,?ship=,?kills=,?tier=URL params.parseDevOverrides()— readswindow.location.searchinto aDevOverridesstruct. Returns{}whenwindowis undefined (SSR safe).SCENARIOS: Map<string, ScenarioBuilder>— module-scope registry, populated byregister(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-builtRunDefinitionornullif the name is unregistered (logsconsole.warnwith available names).listScenarios()— returns all registered names (for dev console help text).baseRun(overrides)— deep-clonesDEFAULT_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-config—RunDefinitiontype andDEFAULT_RUNconstant (cloned per scenario viaJSON.parse(JSON.stringify(...))).window.location.search— only on the browser; guarded bytypeof window === 'undefined'check.
PUSHES TO
- Returns
RunDefinitionobjects to the caller (bridge / dev API). This file does not mutate state directly. - Uses
(def.context as any).devAutoArtifactsand(def.context as any).supplyLevelescape-hatches — these are honored downstream by the bridge dev API / run-init code, not by anything in this file. _devPreSeedKills(referenced in comment onhorde-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_RUNshape — 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
RunDefinitionas-is. - Does not write to disk, network, or storage.
Signals
console.warn('[dev-playground] Unknown scenario: "${name}". Available: ...')onbuildDevScenariomiss.
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;SCENARIOSis fully populated before any caller invokesbuildDevScenario. baseRunis the shared baseline. It enforces dev-friendly defaults: zero fog (darkOpacity = 0,baseRadius = 9999), durable ship (overridable per scenario —damage-testandflame-testreset HP/shield to realistic values), long timer, events unlocked. Scenarios then tweakworldKnobs,spawn,context, orship.combatStatson top.- Deep clone via JSON round-trip.
JSON.parse(JSON.stringify(DEFAULT_RUN))— safe becauseDEFAULT_RUNis 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 }]anddef.node.artifactBoxCount = 0, then tunes world knobs / ship stats to exercise that artifact’s mechanic (e.g.art-fortresslowers HP to make DR visible;art-soullowers enemy HP for fast ghost procs;art-tbone/art-ramboost player thrust for ramming). as anyescape hatches fordevAutoArtifactsandsupplyLevelindicate these are dev-only fields not in the canonicalRunDefinitiontype — they are honored by downstream init code that knows to look for them.planetId+biomepaired. Biome-driven terrain scenarios (sunrise-city,pillar-test,pillar-only) set bothdef.context.planetIdanddef.node.biome— biome drives terrain generation, planet ID drives metadata.pillar-onlyusesplanetId: 99 as anyfor 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 acloneRunDefinition()helper next toDEFAULT_RUN. - Flat-line quantity curve.
[{ time: 0, value: X }, { time: 9999, value: X }]is repeated acrosselite-showcase,telegraph-test,speed-compare,horde-peak,empty-canvas,weapon-showcase. AflatCurve(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: setdevAutoArtifacts, zeroartifactBoxCount, tune knobs. AnartifactScenario(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.
baseRundefaults to a durable ship;damage-testandflame-testreset to realistic values;art-fortressandart-healerpick their own. ArealisticShip(def)ordurableShip(def)toggle could clarify intent, but the override is currently a 3-line block per call site — not yet load-bearing duplication.