scenario-types.ts
PURPOSE
Type-only module. Declares the data shapes for the dev workbench’s scenario/test/matrix subsystem: how a playground scenario is configured, how a test sequence is described, how results and runner state are reported, and how the artifact × tier × scenario matrix is structured. No runtime code, no defaults, no logic — pure contracts consumed by the workbench UI, the scenario runner, and the matrix sweep.
OWNS
ScenarioShip— ship-side knobs: hull, star, weapons (id+level), artifacts (id+tier), upgrades map, stat overrides map, godMode flag.ScenarioWorld— world-side knobs: biome, spawner enable, spawn rate, enemy hp/damage/speed/count multipliers, pre-spawn list (typeId+count), freezeEnemies flag.ScenarioFlow— flow knobs: durationSec, speedMult, autopilot, preSeededKills, startHeat.ScenarioDef— full scenario record: id, name,group(combat | artifacts | vfx | level | stress | custom), description, tags, optional ship/world/flow, optionalpostStartaction list.PostStartAction— discriminated union of six action kinds:grant_artifact,spawn_enemies,set_knob,wait,spawn_crate,fire_signal(withsignal/num1/str1).TestStep— one step in a sequence: scenarioId, optionaloverrides: Partial<ScenarioDef>, optional timeoutSec, optional speedMult.TestSequence— id, name, description, orderedsteps, optional defaultSpeed.TestStatus—'pending' | 'running' | 'pass' | 'fail' | 'timeout' | 'skipped'.TestMetrics— outcome scalars: dps, totalDamageDealt, totalDamageTaken, kills, playerDeaths, artifactTriggers map, fightElapsed, bossHpRemaining, rating, fps, effectTriggerCount.TestResult— stepIndex, scenarioId, status, optional reason, metrics, gameTimeSec, wallTimeSec, timestamp.RunnerState—status(idle | running | paused | complete), currentStep, totalSteps, results, optional currentScenarioId, optionalcurrentRun('A1' | 'A2' | 'B1' | 'B2'— sub-run within an AABB test).ScenarioType—'SWARM' | 'SINGLE_TARGET' | 'SURVIVAL'.ScenarioTemplate— matrix template: type, label,kpi(kills | totalDamageDealt | survivalHp), kpiHigherIsBetter, ship, world, flow, spawnEnemy (string | null), enemiesPerWave, initEnemyCount, spawnDist.MatrixRunMetrics— denser per-run metrics: kills, totalDamageDealt, totalDamageTaken, dps, survivalHp, bossHpRemaining, effectTriggerCount, fightElapsed.MatrixCell— one cell: artifactId, tier, scenarioType, baselineVal, artifactVal, delta, deltaPct, effectTriggerCount, triggered, baselineMetrics, artifactMetrics.TierSweep— artifactId, scenarioType, fixed 4-tuple ofMatrixCell, scalingFactor,scalingShape(linear | exponential | flat | inverted).ArtifactMatrixResult— artifactId, name,sweeps: Partial<Record<ScenarioType, TierSweep>>.MatrixState— status (idle | running | complete), progress (current, total, currentArtifact, currentTier, currentScenario,phase: baseline | artifact), results,baselineCached: Record<ScenarioType, boolean>, wallTimeMs.
READS FROM
Nothing. Type-only module with zero imports.
PUSHES TO
Nothing at runtime. Types are imported by the workbench UI, scenario runner, and matrix runner; this file is erased at build time.
DOES NOT
- Define defaults. Comments document defaults (
speedMultdefault 4,timeoutSecdefaultscenario.flow.durationSec ?? 30) but the file declares no constants — consumers own the fallback values. - Validate. No runtime guards, no Zod schemas, no narrowing helpers — the discriminated union on
PostStartAction['type']is the only enforcement, and it’s structural. - Persist or serialize. No JSON schema, no migration shims, no version field on
ScenarioDef/TestSequence/MatrixState. - Reference game-balance values. Multipliers, tier counts, KPI weights are described in the type but never bound to numbers here.
- Re-export. No barrel; consumers import directly from this module.
Signals
None — type module.
Entry points
import type { ScenarioDef, PostStartAction, ... } from '@/starship-survivors/testing/scenario-types'from workbench React components, scenario runner, matrix runner, and result/report renderers.
Pattern notes
- Tagged-union actions.
PostStartActionis a discriminated union ontype, exhaustively switchable. New action kinds extend the union — keep the discriminator literal-typed. - Optional-everywhere on inputs, required-everywhere on outputs.
ScenarioShip/ScenarioWorld/ScenarioFloware all-optional (scenarios opt in to knobs);TestResult,MatrixCell,MatrixRunMetricsare required-field (outputs are dense, not partial). - AABB sub-run labels.
RunnerState.currentRunuses the'A1' | 'A2' | 'B1' | 'B2'literal-union pattern — two baseline runs (A), two with-artifact runs (B). The runner module owns the semantics; this file only carries the label. - Fixed 4-tuple for tiers.
TierSweep.cells: [MatrixCell, MatrixCell, MatrixCell, MatrixCell]encodes the four artifact tiers structurally. Length is a type-level invariant, not a runtime check. Partial<Record<ScenarioType, TierSweep>>onsweeps. Allows partial matrix completion (e.g. SWARM done, SURVIVAL still running) without sentinel values.scalingShapeis a closed literal union (linear | exponential | flat | inverted). New shapes are a breaking change to consumers — intentional.kpiandkpiHigherIsBetterco-vary by convention, not by type. Comment notes bothkills/damageandsurvivalHpare higher-is-better — the field exists so future KPIs can flip the polarity.effectTriggerCountappears on bothTestMetricsandMatrixRunMetricswith the same meaning (artifact effect-engine triggers, 0 for baseline). Same field name, different containers.
EXTRACT-CANDIDATE
- Runner-status literal unions.
RunnerState.status('idle' | 'running' | 'paused' | 'complete') andMatrixState.status('idle' | 'running' | 'complete') overlap. If a third runner appears, hoist a sharedRunnerLifecycletype. - Metric shape duplication.
TestMetricsandMatrixRunMetricsoverlap on kills / totalDamageDealt / totalDamageTaken / dps / bossHpRemaining / effectTriggerCount / fightElapsed. If the workbench unifies its result pipeline, extract aBaseMetricsand let each layer extend it. - Scenario-knob bag pattern.
ScenarioShip/ScenarioWorld/ScenarioFloware three all-optional knob bags merged intoScenarioDef. If a fourth axis appears (e.g.ScenarioInputfor autopilot tuning), formalize the “knob bag” shape as a mapped type.