data

PURPOSE — Read-only catalog of game content (weapons, enemies, bosses, artifacts, ships, mods, passives, planets) and the shared schemas everything downstream consumes. Pure TypeScript with no runtime side effects beyond module-load asserts; importable in a Vitest test environment without touching window, document, localStorage, or any store. The foundation everything else builds on — engine and metagame read from data, never the reverse.

OWNS

  • Per-domain item registries built from the one-file-per-item pattern: each domain folder holds an _types.ts (item type definition), an index.ts (assembles the array and lookup map, runs module-load invariant asserts, and exposes both), and one source-of-truth file per item.
  • The five primary content registries — artifacts, weapons, enemies, mods, passives — and the boss registry that self-registers per-file via top-level mutation of a shared map.
  • Lookup maps from item id to definition for every registry, built at module load by walking the array and indexing into a Record<string, Def>.
  • Top-level lookup tables that hold rosters / palettes / tuning curves and frequently re-export per-domain folders: ships and hull classes, planet roster, nebula archetypes, level presets, terrain shapes, theme palette tokens, weapon icons, hull hitboxes, ship rarity metadata, kill streaks, pull rates and configs, boss scaling curves, reward cards, reward types, progress key constants, prologue script, run modifiers, economy constants.
  • Schema / contract types that define the shape of data consumed elsewhere: the run definition (engine input), the mission result (engine output), per-level configuration, the persistent player save format with versioning, run history records, currency / resource id enums, per-ship and per-planet progression curves, per-planet challenge definitions.
  • Enemy type generation: an 8 × 5 rarity cube produced by walking the base archetype list against the rarity multiplier table, plus a hand-authored boss-roster patch list pushed onto the cube to give faction-specific bodies their own typeIds, plus the hull-shape map that routes typeId → polygon archetype for the renderer.
  • Spawn pool tables banded by run progress (default and per-planet variants), with elite-leader pickers and per-set leader archetype lists.
  • The save migration runner: a version-keyed migration map, a current-version constant, and a runner that throws if no migration is registered for an intermediate version (no silent fallback).
  • Boss scaling math: per-stat dimension configs (uncapped / soft_cap / hard_cap), the dimension resolver, and the default scaling config for all bosses.
  • Run-level progression sequences: the fixed 5-step normal arc and the 10-step challenge arc, plus the LevelKind resolver and final-level test, plus sealed-arena geometry and boss-kind HP / damage multipliers.
  • Run history persistence: localStorage-backed FIFO buffer of compact run summaries, per-node personal-best keys for tier / kills / events / level, the new-account spawn dampener curve, run averages, and personal-best extractors. The single boundary file in data/ that touches localStorage — it lives here because the schema is the canonical run-history record shape, not because it does any game logic.
  • Color tokens (raw + semantic), rarity palette, font stacks, VFX trait library, and a number-formatter utility.
  • Constants for tier color palettes, rarity multipliers, runtime-vs-data tier index bridging, flat-bonus percentage ramps by tier, per-artifact flat-bonus stat mappings.

READS FROM — nothing internal. Data is the leaves of the dependency tree. The purity rule (in data/README.md) forbids importing from ../engine/, ../stores/, ../services/, ../screens/, ../components/, or any browser API at module load. The only outbound imports are intra-domain (top-level weapons.ts re-exports the weapons/ folder; the artifact effect-def field references an engine effect type via a import('...') type-only reference).

PUSHES TO — none. Read-only by contract. Every consumer pulls from the exposed maps / arrays / helper functions; the data layer never mutates external state, never fires signals, never writes to stores, never calls into the engine.

DOES NOT

  • Mutate any state at runtime. The only writes that happen at module load are populating lookup maps from arrays and running invariant asserts.
  • Perform I/O, network calls, Supabase round-trips, or async work at module load.
  • Import browser APIs (window, document, localStorage, navigator, fetch) except in the one file whose schema explicitly is the localStorage-backed run-history record — and even there the storage access is gated behind try/catch so the surrounding module load can’t fail.
  • Hold runtime game state (active bullets, live enemies, ship instance) — that lives in the engine and stores.
  • Decide which enemy spawns next, which weapon fires, which artifact procs — those are engine concerns that read from these tables.
  • Render anything, play audio, or queue VFX — only defines the palette tokens, VFX trait library, and per-item color metadata the renderers consume.
  • Validate gameplay legality (e.g. “can this ship equip this weapon”) — only the engine / metagame enforces those rules using the read-only metadata exposed here.
  • Define implementation behavior for artifacts, mods, passives, or weapon archetypes — those handlers live in engine/; this layer only carries the named numeric params and ids.
  • Resolve player progression, account state, or mission selection — only defines the contract shapes those systems pass through.

Signals fired / Signals watched — none. Read-only data has no event surface.

Entry points

  • WEAPONS, WEAPON_MAP, WEAPON_ORDER — array, id-to-spec lookup, and ordered id list for the weapon roster (including legendary fusions appended at the end).
  • LEGENDARY_WEAPONS, LEGENDARY_PAIR_MAP, getLegendaryForPair, pairKey, sortTagPair — fusion-result resolution.
  • RARITY_COLORS (weapons) — UI hex colors per rarity tier.
  • getExtraProjectiles — bonus-projectile resolver from the horizontal upgrade count, table-driven per weapon id.
  • getEffectiveLevel, getWeaponStatAtLevel, getSteppedStatAtLevel, getProbabilisticSteppedStat, getVfxTier, getWeaponDamageMult, LEGENDARY_DAMAGE_BASELINE_MULT — per-weapon stat-pipeline helpers.
  • ENEMY_WEAPON_STATS — enemy-side weapon stat table.
  • resolveWeaponRarity — flattens stored rarity to 'common' | 'legendary'.
  • ENEMY_TYPES, ENEMY_TYPE_MAP — the full 8 × 5 archetype-rarity cube plus the appended boss-roster bodies, indexed by typeId.
  • SPAWN_POOLS, getSpawnPool, getSpawnPoolForSet, pickEliteForSet, pickEliteForProgress, SET_SWARM_ARCHETYPE — progress-banded spawn-pool resolvers, including per-planet variant pools.
  • RARITY_TINTS, RARITY_MULTS, RARITY_ORDER, ENEMY_TIER_COLORS, PERSONALITY_PROFILES — enemy rarity scaling and presentation metadata.
  • ENEMY_COLLISION_SCALE, getEnemyCollisionRadius — collision-radius helper.
  • HULL_SHAPE_MAP — typeId-to-polygon-archetype routing for the renderer, with explicit entries per rarity per archetype plus per-boss-roster passthrough.
  • ARTIFACT_DEFS, ARTIFACT_MAP — array and lookup of artifact definitions across the three categories (unique, stat, weapon).
  • ARTIFACT_TIER_NAMES, ARTIFACT_TIER_MAX, ARTIFACT_TIER_COLORS, ARTIFACT_TIER_COLOR_BY_IDX, FLAT_BONUS_PCT_BY_TIER, getTierValuesAt, getTierLabelAt, ARTIFACT_FLAT_BONUSES, getArtifactFlatBonus, getArtifactFlatBonusValueAt — runtime-tier-to-data-tier bridging, per-tier flat-bonus ramp resolution, per-artifact stat-mapping lookup.
  • MOD_TEMPLATES, MOD_TEMPLATES_BY_ID — array (frozen) and lookup of the mod template catalog.
  • RARITY_MULTIPLIER, RARITY_ORDER, RARITY_COLORS (mods), DROP_RARITY, nextRarity, shape, rectCells, iterateFilledCells, filledCellCount, validateShape — mod-shape primitives and rarity scaling.
  • PASSIVES, getPassiveValue, getPassiveDescription, resolvePassive, RARITY_INDEX — passive-by-id lookup and rarity-aware value resolution.
  • BOSS_DEFS, bossDefKind, pickRandomBossId — boss registry mutated by per-file self-registration, plus the kind classifier and pool picker.
  • HULL_CLASSES, getShipDef, toShipCombatStats, toShipMetaStats — ship/hull roster lookup and stat extraction.
  • PLANETS, PLANET_ORDER, PlanetDef — planet roster.
  • DEFAULT_RUN, validateRunDef, RunDefinition (and its sub-interfaces — ShipCombatStats, ShipMetaStats, FacilityBonuses, WorldKnobs, SessionBonuses, CodexBonuses, MissionObjective, BossConfig, VisionConfig, EventPoolConfig, StartingWeapon) — the engine input contract and its assertion-based validator.
  • EVENT_KILL_THRESHOLD — per-run kill gate before sub-events spawn.
  • MissionResult — the engine output contract consumed by the results screen.
  • LevelConfig, DEFAULT_LEVEL_CONFIG, DEFAULT_SPAWN_ZONES, DEFAULT_PALETTE, Hub, Spoke, PrecomputedWorld, ZoneType, PatternType, SpokeStyleId, HubStyleId, TerrainTypeId, HubParticleType, LanternPresetId, WorldMode, LevelTrigger, TriggerType, TriggerPayload, ColorPalette, SpawnZoneConfig, ZonePool, ZonePoolEntry, SpokeFlowConfig, ScriptedHub, ScriptedSpoke — per-level configuration, defaults, hub/spoke runtime types, and the trigger contract.
  • RUN_LEVEL_SEQUENCE, CHALLENGE_LEVEL_SEQUENCE, LevelKind, resolveLevelKind, isFinalLevel, SEALED_ARENA_HALF_SIZE, SEALED_ARENA_WALL_THICKNESS, BOSS_KIND_HP_MULT_MINI, BOSS_KIND_HP_MULT_BOSS, BOSS_KIND_DAMAGE_MULT_MINI, BOSS_KIND_DAMAGE_MULT_BOSS — run-level progression sequence helpers plus arena geometry and boss-kind multipliers.
  • BossScalingConfig, ScalingDimension, CapType, ResolvedBossScaling, computeDimension, resolveBossScaling, DEFAULT_BOSS_SCALING, BASE_ENRAGE_TIMER_SEC — per-stat scaling resolver with three cap behaviors.
  • SaveBlob, CURRENT_SAVE_VERSION, runSaveMigrations — persistent save schema and version-gated migration runner.
  • RunHistorySummary, saveRunToHistory, getRunHistory, getNewAccountSpawnMult, getRunAverages, getPersonalBests, getTierPB / saveTierPB, getKillsPB / saveKillsPB, getEventsPB / saveEventsPB, getLevelPB / saveLevelPB — local run-history record, FIFO save, new-account dampener, averages / PBs, and strictly-improving per-node personal-best persistence.
  • PROGRESS_KEYS, ProgressKey — cumulative-stat tracking keys used by the achievement system and rookie-week curve.
  • SP, THEME, RARITY_COLORS (theme), VFX_TRAITS, formatNum, RarityColor, VfxTrait — palette tokens, semantic UI theme, rarity colors used app-wide, named VFX trait library, and the K/M number formatter.
  • Top-level re-exports — weapons.ts, artifacts.ts, enemies.ts, passives.ts, mod-templates.ts are barrels that re-export from their per-domain folders so older consumers keep importing from the flat path while new code may use either.

Pattern notes

  • Every primary content domain follows the one-file-per-item registry pattern: _types.ts defines the item type, _helpers.ts (optional, currently only in weapons/) holds shared math, index.ts imports each item module, builds the array and lookup map, runs any module-load invariants, and exports both, and each <item-id>.ts exports a single named const matching the item type. Per-item files are the source of truth — no item data lives in index.ts.
  • Module-load throws for invariant assertion are encouraged and used in practice (mods validate shape, assert id uniqueness, and assert the expected template count). They catch contract violations before any test or build step touches the data.
  • Lookup maps are built imperatively in a tight loop right after the array literal — for (const x of ARRAY) MAP[x.id] = x; — and are exported as plain Record<string, Def>. The mod map is additionally Object.freezed.
  • Top-level files split into two flavors: registries / lookup tables (ships.ts, planet-config.ts, nebula-archetypes.ts, level-presets.ts, terrain-shapes.ts, theme.ts, weapon-icons.ts, hull-hitboxes.ts, ships-v4-rarity.ts, kill-streaks.ts, pull-rates.ts, pull-config.ts, boss-scaling.ts, reward-cards.ts, reward-types.ts, progress-keys.ts, prologue-config.ts, modifiers.ts, economy.ts) and schemas / contracts (run-config.ts, mission-result.ts, level-config.ts, save-schema.ts, save-migrations.ts, run-history.ts, resources.ts, ship-progression.ts, planet-progression.ts, challenges.ts). New top-level files should pick a flavor before being added and group with their peers.
  • Top-level files like weapons.ts, artifacts.ts, enemies.ts, passives.ts, mod-templates.ts are re-export barrels — the actual definitions live in the per-domain folders, but older import paths keep working. New code may import from either.
  • The boss registry inverts the pattern: instead of index.ts importing definitions and building the map, each per-boss file is imported for side effect and then explicitly assigned into the shared BOSS_DEFS map via top-level mutation. The lookup is read-mostly afterward.
  • Enemy types are generated, not enumerated: 8 base archetypes × 5 rarities are produced by buildEnemyTypes() at module load, walking RARITY_ORDER and applying RARITY_MULTS to each base. Archetype-specific tuning (AOE, blast, field, sniper, burst) is grafted on inside the loop using if (base.archetype === ...) branches. Faction-specific boss bodies are then appended via ENEMY_TYPES.push(...BOSS_ENEMY_TYPES).
  • Spawn pools are stored as banded arrays keyed by progress min/max, walked in reverse to find the latest band the progress value satisfies. Per-planet variants live in VARIANT_POOL_MAP and override the default; sets that aren’t in the map fall back to SPAWN_POOLS (or CITY_SPAWN_POOLS for the city set).
  • Curve-based scaling (boss-scaling, weapon scaling) uses tent-pole keyframe arrays of { time, value } pairs that the engine lerps between. Out-of-bounds inputs clamp to the nearest pole, not throw.
  • The save migrations file enforces “no silent fallback”: if the runner encounters a save_version below CURRENT_SAVE_VERSION with no registered migration, it throws. The migration map is a Record<number, Migration> where each key is the from-version and the function returns the next-version blob.
  • Run-level progression resolves a LevelKind enum via a fixed sequence array indexed by currentLevel - 1, clamped to the final entry for out-of-bounds inputs so dev mode never crashes the engine. Challenge mode swaps the sequence array.
  • RunDefinition is the single source of truth for the engine input contract — every field is required (no optionals except a handful of explicitly marked dev/debug knobs), and validateRunDef is a flat assertion list that throws on missing / out-of-range fields rather than coercing.
  • MissionResult mirrors the same shape on the way out: a frozen contract with a __version discriminant and a __contract name, every field required, consumed by the results screen and downstream progression / economy systems.
  • The artifact tier system has a 5-tier runtime ladder (common, uncommon, rare, epic, legendary) but data files carry a 4-tuple tiers array (uncommon → legendary); getTierValuesAt bridges the index gap by clamping the runtime tier into the data range. Differentiation between runtime common and uncommon comes from the linear flat-bonus ramp in FLAT_BONUS_PCT_BY_TIER, not from per-tier ability values.
  • ARTIFACT_FLAT_BONUSES is a parallel map alongside ARTIFACT_MAP that maps artifact id → flat-stat-bonus descriptor. The runtime stat palette is intentionally small (weaponDamagePct, fireRatePct, maxSpeed, magnetRange, luck, damageReduction), so artifacts in the same archetype share a stat — the repeats across the table are intentional.
  • The run-history.ts file is the single exception to the “no browser APIs” purity rule, because its entire purpose is to define and persist the run-history schema in localStorage. All localStorage access is wrapped in try/catch so failures degrade silently rather than crash module load. Personal-best writes are strictly improving — they return false on stale value, invalid input, or storage failure.
  • Rarity ordering is canonical: ['common', 'uncommon', 'rare', 'epic', 'legendary'] is used everywhere (enemies, mods, ships, artifacts) as the source of truth for index-to-name resolution. WoW-style hex colors are reused across most rarity-color maps (artifacts, mod drops, weapons), with theme.ts’s RARITY_COLORS being a separate green/blue/epic/legend palette for the older 4-tier system.
  • Tier-tagged additions to the spawn-pool tables (Sprinter, Spitter, Burner, Suppressor at ticks 48–52, Brute / Wisp / Lurker / Bombardier at ticks 25–26) reuse existing archetype behaviors and polygon shapes; the HULL_SHAPE_MAP carries explicit per-archetype-per-rarity entries plus a backfill for the t25/t26 cohort that previously relied on a polygon fallback.
  • The data layer is the contract — engine and metagame call into it, never the reverse. If a file in data/ needs to import from engine/ or stores/, the layering is broken and the file belongs in services/ instead.