background-loader.ts
PURPOSE
Loads every BackgroundDef at module-load time from two sources, validates each entry, and exposes them as a sorted readonly array plus an id-keyed map. Bad JSON throws synchronously at import — authoring mistakes surface the moment the dev tool boots, never at bake time.
OWNS
loaded: BackgroundDef[]— internal mutable accumulator, frozen after sort via the readonly export.ALL_BACKGROUNDS: readonly BackgroundDef[]— id-sorted (localeCompare) catalog of every validated def.BACKGROUND_MAP: ReadonlyMap<string, BackgroundDef>— id → def lookup built fromALL_BACKGROUNDS.getBackground(id)— id lookup that throws with the full available-id list on miss.
READS FROM
./background-schema—validateBackgroundDef,BackgroundDeftype.../../data/backgrounds/*.background.json— Vite eagerimport.meta.glob, default export, source 1.../../data/backgrounds/v2-catalog.json— static import, array ofBackgroundDef, source 2.
PUSHES TO
- Nothing at runtime. Pure module-level data. Consumers import
ALL_BACKGROUNDS,BACKGROUND_MAP, orgetBackground.
DOES NOT
- Does not load lazily — every def is parsed/validated at first import.
- Does not deduplicate ids across the two sources; a collision would just leave the later-inserted entry visible via
BACKGROUND_MAP(Map overwrite on construction) while both occupy slots inALL_BACKGROUNDS. - Does not log; it throws.
- Does not bake, transform, or normalize defs —
validateBackgroundDefis the only gate. - Does not handle async — no fetch, no IO beyond the bundler’s static imports.
Signals
- Source 1 error:
BackgroundDef invalid (<path>): <reason>— thrown when an individual*.background.jsonfails validation. - Source 2 error:
v2-catalog[<i>] (<id-or-?>) invalid: <reason>— thrown for any failing catalog entry, with array index and best-effort id. - Lookup miss:
background '<id>' not found. Available: <comma-list>— thrown bygetBackgroundwith the sorted available-id list.
Entry points
import { ALL_BACKGROUNDS } from '.../background-loader'— full sorted list.import { BACKGROUND_MAP } from '.../background-loader'— direct map access.import { getBackground } from '.../background-loader'— single-id fetch with crash-on-miss.
Pattern notes
- Crash-on-bad-data, no silent fallbacks — matches the project rule: internal config validation throws, defensive logic only at user/IO/API boundaries.
- Two-source design lets v2 ship ~100 backgrounds via a single
v2-catalog.jsonarray without minting 100 separate files, while keeping the old per-file path open for legacy entries. import.meta.glob(..., { eager: true, import: 'default' })is the Vite idiom for bundling a folder of JSON at build time; the loader assumes a Vite environment.- Sort is stable via
localeCompareonid; downstream code can rely onALL_BACKGROUNDSorder for deterministic iteration in tools. - Cast pattern
mod as unknown as BackgroundDefis used post-validation; the schema function is the type-narrowing authority, not TS inference.
EXTRACT-CANDIDATE
- Two-source-with-validate-and-sort loader pattern is a duplicate of the same shape used elsewhere in the engine (e.g. enemy/weapon/affix loaders). A generic
buildStaticCatalog<T>({ glob, catalog, validate, idOf })helper could collapse the duplication if it appears in 3+ loaders. Defer until the third instance is confirmed. - Catalog-vs-files dedup policy is currently implicit. If id collisions become a real risk, add an explicit
Set<string>check during insert and throwduplicate background id '<id>' from <source>.