BUILD_VERSION

BUILD_VERSION is a single string constant baked into the Vite bundle at build time. It is the canonical identifier for “which build of the game is running right now,” used for cache invalidation, error attribution, and on-screen build stamping. It is bumped on every push to main that touches game code.

Where it lives

BUILD_VERSION is defined in src/starship-survivors/engine/core/config/_version.ts as a string literal (e.g. 'v5.285.0'). The wider engine/core/config.ts and engine/core/config/index.ts barrels re-export it, so every consumer imports it from @starship-survivors/engine/core/config. There is no environment lookup, no process.env read, and no Vite define substitution — the value is a hardcoded TypeScript export, edited and committed each release.

What it does

The constant has four distinct loads, all from the same string.

1. Asset cache-busting. getWeaponIconPath() in src/starship-survivors/data/weapon-icons.ts appends ?v=${BUILD_VERSION} to every weapon and modifier icon URL (e.g. /weapons/rifle.png?v=v5.285.0). This forces the browser, Service Worker, and CDN to treat each release’s assets as new URLs, preventing stale PNGs from being served when the underlying file has changed. The query string is meaningless to the static-file server but unique enough to bypass cache layers.

2. Sentry release tag. src/main.tsx calls Sentry.init({ release: BUILD_VERSION, ... }) at boot. Every captured exception, message, and trace is tagged with the build it came from, so the Sentry dashboard can group errors per release and tell “new in v5.285.0” from “still leaking since v5.230.0.”

3. Telemetry attribution. The telemetry sender (engine/telemetry/sender.ts), sampler (engine/telemetry/sampler.ts), and diagnostic logger (engine/telemetry/diag.ts) all stamp app_version: BUILD_VERSION onto every event payload sent to Supabase. This is how the analytics side correlates performance regressions, crash rates, and feature funnels with specific releases.

4. Dev/HUD display. The metagame shell (metagame/app/App.tsx and metagame/components/V32Shell.tsx) renders BUILD_VERSION as a corner stamp so Nate can read the running build at a glance during testing. metagame/components/UpdateBanner.tsx parses the short vX.Y.Z form via regex and compares it to the version reported by the deployed index.html to detect when the player’s tab is running stale code.

The perf-flags hybrid

engine/core/config/_perf-flags.ts builds a derived constant PERF_VERSION_TAG that concatenates BUILD_VERSION with any active performance feature flags (e.g. v5.285.0-noShadows-lowParticles). Telemetry uses PERF_VERSION_TAG instead of raw BUILD_VERSION when flag-controlled experiments are running, so A/B slices can be separated on the analytics side. When no flags are active, PERF_VERSION_TAG === BUILD_VERSION.

Bump discipline

Bumping BUILD_VERSION on every push to main that touches game code is hard rule #8 in CLAUDE.md and step 1 of the push process. The /git-push skill encodes it. Skipping the bump breaks all four downstream systems silently: cache invalidation no-ops (players see old assets), Sentry groups multiple releases together (regression attribution dies), telemetry shows the previous release’s version (analytics lies about what’s deployed), and the dev HUD reports the old number (Nate can’t tell which build is live).

Format

The current value follows vMAJOR.MINOR.PATCH (e.g. v5.285.0). The major version reflects large architecture eras; minor increments on every release; patch is reserved for hotfix pushes between minor bumps. UpdateBanner.tsx’s regex /v\d+\.\d+\.\d+/ is the only consumer that depends on this shape — everything else treats the value as an opaque string.

Why a hardcoded constant, not a build-time injection

Vite supports define substitutions and reads from package.json, either of which could populate BUILD_VERSION automatically. The game uses a hardcoded constant instead for two reasons. First, the bump is a deliberate gate: editing the file forces the author to acknowledge “this is a release” rather than letting CI silently auto-stamp every push. Second, the value needs to survive type-checking, tests, and local dev runs identically — a define substitution that only fires in vite build would leave dev builds with a different version string than production, making Sentry/telemetry filters non-portable across environments.

See also