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, optional postStart action list.
  • PostStartAction — discriminated union of six action kinds: grant_artifact, spawn_enemies, set_knob, wait, spawn_crate, fire_signal (with signal/num1/str1).
  • TestStep — one step in a sequence: scenarioId, optional overrides: Partial<ScenarioDef>, optional timeoutSec, optional speedMult.
  • TestSequence — id, name, description, ordered steps, 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.
  • RunnerStatestatus (idle | running | paused | complete), currentStep, totalSteps, results, optional currentScenarioId, optional currentRun ('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 of MatrixCell, 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 (speedMult default 4, timeoutSec default scenario.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. PostStartAction is a discriminated union on type, exhaustively switchable. New action kinds extend the union — keep the discriminator literal-typed.
  • Optional-everywhere on inputs, required-everywhere on outputs. ScenarioShip / ScenarioWorld / ScenarioFlow are all-optional (scenarios opt in to knobs); TestResult, MatrixCell, MatrixRunMetrics are required-field (outputs are dense, not partial).
  • AABB sub-run labels. RunnerState.currentRun uses 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>> on sweeps. Allows partial matrix completion (e.g. SWARM done, SURVIVAL still running) without sentinel values.
  • scalingShape is a closed literal union (linear | exponential | flat | inverted). New shapes are a breaking change to consumers — intentional.
  • kpi and kpiHigherIsBetter co-vary by convention, not by type. Comment notes both kills/damage and survivalHp are higher-is-better — the field exists so future KPIs can flip the polarity.
  • effectTriggerCount appears on both TestMetrics and MatrixRunMetrics with 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') and MatrixState.status ('idle' | 'running' | 'complete') overlap. If a third runner appears, hoist a shared RunnerLifecycle type.
  • Metric shape duplication. TestMetrics and MatrixRunMetrics overlap on kills / totalDamageDealt / totalDamageTaken / dps / bossHpRemaining / effectTriggerCount / fightElapsed. If the workbench unifies its result pipeline, extract a BaseMetrics and let each layer extend it.
  • Scenario-knob bag pattern. ScenarioShip / ScenarioWorld / ScenarioFlow are three all-optional knob bags merged into ScenarioDef. If a fourth axis appears (e.g. ScenarioInput for autopilot tuning), formalize the “knob bag” shape as a mapped type.