Skip to content

v0.1 Warden-Only Boss + Post-Kill Teaser Wormhole — Design

v0.1 Warden-Only Boss + Post-Kill Teaser Wormhole — Design

Section titled “v0.1 Warden-Only Boss + Post-Kill Teaser Wormhole — Design”

Date: 2026-07-01 Status: Approved (design); ready for implementation plan Mode: Survival only (the only mode reachable in v0.1 — story mode is gated off by StartMenu.V01_CRYSTALS_ONLY)

For the v0.1 launch, survival only ever spawns the Warden (the original boss) — none of the other four bosses (Sentinel/Boss2, FunZo, Graviton, The Eye). The first time the Warden dies in a run, a wormhole opens at its death spot (reusing the existing portal visual). Flying through it shows a full-screen “teaser” overlay — a procedural spiral-galaxy backdrop, a glowing silhouette + name of one of the four locked bosses (picked at random), and a small strip of 2-3 existing enemy names as a “coming soon” preview — then the overlay dismisses and the run continues exactly where it left off. Purely a flavor/marketing moment: no area change, no gameplay effect, nothing unlocked.

Chris’s explicit forward intent: this same system gets repointed for v0.2 → v0.3 (new bosses/enemies teased next). The teased content pools must therefore be trivial to swap later, not hardcoded into logic.

  1. Warden-only for v0.1, gated by a single flag next to the existing V01_LOCK_AREAS/V01_LOCK_COOP/V01_CRYSTALS_ONLY flags — flip it to restore the full 5-boss rotation post-launch. Boss2/FunZo/Graviton/Eye code is untouched, just unreachable from survival’s boss picker while the flag is on.
  2. Teaser wormhole is a system fully separate from the real explorable-areas wormhole/warp system (option 2 from the approach discussion) — same portal visual, but its own spawn condition, its own event, its own overlay class. The real area system (Sim.wormholes, Sim.areas_enabled, the "warp" event, Sim.enter_area) is not touched at all and stays fully locked. This is deliberately a throwaway-safe boundary: nothing here has to be unwound when the real explorable-areas feature eventually ships.
  3. Once per run. Only the first Warden kill in a run spawns the teaser wormhole; subsequent Warden kills that run (it still respawns on the usual boss-interval cadence) do not spawn another.
  4. Random pick, config-driven pools. One boss is picked at random from a TEASER_BOSS_POOL const array; up to 3 enemies are picked at random from a TEASER_ENEMY_POOL const array (bestiary ids). For v0.2, repointing this system at v0.3’s content is editing those two arrays — nothing else changes.
  5. Reuses existing content — no new enemies or bosses are designed for this. The four locked bosses and the enemy pool already exist in full; the teaser only presents their existing name/color/bestiary text.
  6. Full-screen takeover layout (validated via mockup): spiral-galaxy backdrop fills the screen, a large glowing boss silhouette + name centered, a small row of enemy name badges along the bottom, a short title banner at the top.
  7. Pauses the run, same idiom as the level-up/pause-menu freeze gates. Auto-dismisses after a few seconds, or immediately on any confirm press; the run then resumes exactly where it left off (no i-frame grace needed — nothing was mid-fight when the player deliberately flew into the portal).

Load-bearing constraint — determinism baseline unaffected

Section titled “Load-bearing constraint — determinism baseline unaffected”

The Warden already only spawns past BOSS_FIRST_TIME (210s) and the teaser fires only on its death — both far outside the pinned 10s/600-tick determinism window. No new rng draws occur before that window. The pinned baseline (snapshot_string().hash() = 2730172591, state_checksum() = 4075578713 as of the 2026-07-01 swarmer-buff re-pin) must stay byte-identical; re-verified as part of the build ritual with zero expected movement.

/sim changes (pure logic, no Node/render APIs)

Section titled “/sim changes (pure logic, no Node/render APIs)”
  • sim/sim.gd_maybe_spawn_survival_boss(): add const V01_WARDEN_ONLY := true near the existing boss-rotation logic. When true, always calls _spawn_boss(s) and returns before the % 5 rotation match. Flip to false to restore the full rotation.
  • sim/sim.gd — new teaser state:
    • const TEASER_BOSS_POOL: Array[int] = [EnemyPool.TYPE_BOSS2, EnemyPool.TYPE_FUNZO, EnemyPool.TYPE_GRAVITON, EnemyPool.TYPE_EYE]
    • const TEASER_ENEMY_POOL: Array[String] = ["ghost", "accumulator", "orbiter"] (bestiary ids; a starting set Chris can extend)
    • var teaser_wormhole: Dictionary = {}{pos} when active, empty otherwise (separate from wormholes, which stays reserved for the real area system)
    • var _teaser_shown_this_run: bool = false — the once-per-run latch
    • var teaser_event: Dictionary = {} — set once when the player flies through ({boss_type: int, enemy_ids: Array[String]}); render side reads + clears it each frame it’s non-empty (same one-shot-event idiom as fx_events, just not cleared unconditionally every tick since it must survive until read)
    • _spawn_teaser_wormhole(pos): no-ops if not V01_WARDEN_ONLY or _teaser_shown_this_run or a teaser wormhole is already out; otherwise sets _teaser_shown_this_run = true and teaser_wormhole = {"pos": pos}. Called from _sweep_dead alongside (NOT instead of) the existing _spawn_area_wormhole(boss_died_at) call — both are independently gated (the real one still no-ops on areas_enabled == false), so they can never both actually spawn a wormhole in v0.1.
    • _update_teaser_wormhole(): proximity check (same WORMHOLE_RADIUS as the real one) against teaser_wormhole; on entry, picks rng.randi_range(0, TEASER_BOSS_POOL.size()-1) for the boss and samples up to 3 distinct entries from TEASER_ENEMY_POOL (shuffle-and-take, mirroring the existing crystal-pool shuffle pattern already in roll_upgrade_choices), fills teaser_event, clears teaser_wormhole. Called from tick() alongside the existing _update_wormholes().
    • consume_teaser_event() -> Dictionary: returns and clears teaser_event (render calls this once when it actually shows the overlay, so a dropped frame — e.g. another overlay is up — doesn’t lose the event; mirrors how the enemy-codex feature guards against “seen” being marked before the card actually shows).
  • main.gd: feed sim.teaser_wormhole into the existing WormholeRenderer.update_wormholes() call alongside sim.wormholes (simple concat — the renderer already just draws whatever positions it’s given, no changes needed there). Each _process, if sim.teaser_wormhole is empty but sim.teaser_event is non-empty and no other overlay/freeze is active: call sim.consume_teaser_event(), resolve _base_color_for_type(event.boss_type, -1) and EnemyPool.TYPE_NAMES[event.boss_type] for the boss, resolve each event.enemy_ids entry to its bestiary display name, and call boss_teaser.show_for(color, name, labels). Add boss_teaser.is_open() to the _physics_process freeze gate (same idiom as weapon_info.is_open()/codex.is_open()).
  • ui/boss_teaser_overlay.gd (new, BossTeaserOverlay extends CanvasLayer):
    • show_for(boss_color: Color, boss_name: String, enemy_labels: Array[String]): builds the takeover — title banner, a spiral galaxy _draw() backdrop (procedural, additive arcs/dust rotating slowly — same vector-art idiom as funzo_renderer.gd/boss2_renderer.gd, not a new image asset), the teased boss’s glowing silhouette in boss_color + boss_name centered, and a row of enemy_labels badges (bestiary name text, no new art) along the bottom. Takes already-resolved presentation values, not a raw sim.teaser_eventmain.gd resolves boss_color via the existing _base_color_for_type() helper before calling in. This keeps the overlay a pure presentation class with zero coupling to Sim/EnemyPool, so it stays trivially reusable for a v0.2 teaser with entirely different content.
    • Auto-dismiss timer (a few seconds) OR any confirm press dismisses early.
    • is_open() -> bool for the freeze gate.
Warden dies (first time this run) ─▶ Sim._spawn_teaser_wormhole(pos)
player flies over it ─▶ Sim._update_teaser_wormhole()
│ picks random boss + up to 3 enemies
Sim.teaser_event = {boss_type, enemy_ids}
main._process (no other overlay open) ─▶ consume_teaser_event()
│ resolve colour/name/labels
BossTeaserOverlay.show_for(color, name, labels)
│ (sim paused)
auto-dismiss timer / confirm press
run resumes, unchanged

One-way, render-only past the single teaser_event handoff. No area change, no meta-save write, no gameplay effect.

  • tests/test_v01_warden_only.gd (new): _maybe_spawn_survival_boss never spawns Boss2/FunZo/Graviton/Eye across many calls while V01_WARDEN_ONLY is true; always spawns TYPE_BOSS.
  • tests/test_teaser_wormhole.gd (new):
    • A Warden death spawns exactly one teaser wormhole; a second Warden death the same run does not spawn another (_teaser_shown_this_run latch).
    • Flying over it produces a teaser_event with boss_type drawn from TEASER_BOSS_POOL and enemy_ids a subset (≤3, all distinct) of TEASER_ENEMY_POOL.
    • consume_teaser_event() returns the event once then reads empty.
    • The real wormholes array and areas_enabled are untouched by any of the above (confirms the two systems are genuinely independent).
  • Determinism — re-run tests/test_determinism_checksum.gd + test_determinism_crystals.gd and confirm zero movement (Warden-only + teaser logic both fire far outside the pinned window).
  • Test-count guardscripts/check-test-count.sh after adding the new test files.
  • Boot smoke--headless --quit-after + grep stderr for SCRIPT ERROR; an extended smoke run is not expected to reach 210s (BOSS_FIRST_TIME), so the teaser path itself is verified by the unit tests above, not the smoke run.
  • No GUT test for BossTeaserOverlay itself (render/UI, matches this project’s established convention — verified by boot-check + eventually a live playtest).
  • No new bosses, no new enemies, no new art assets — everything teased already exists in full.
  • No persistence of “which boss was teased” — it’s a fresh random pick every run, nothing tracked in the meta save.
  • No skip-tracking (“don’t show me this again”) — it’s once-per-run by design, low enough frequency not to need a permanent opt-out for v0.1.
  • The real explorable-areas/wormhole system is not modified, extended, or partially unlocked in any way.
  • No audio cue beyond whatever the existing portal/reaction stinger already covers (reuse, don’t add).
  • v0.2: repoint TEASER_BOSS_POOL/TEASER_ENEMY_POOL at v0.3’s new content once it exists (the whole point of building this generically now).
  • Eventually replacing “random pick” with a fixed narrative order, if the story team wants the tease to always show a specific “next” boss rather than a surprise.
  • Real area unlock (already specced separately in docs/superpowers/specs/2026-06-28-explorable-areas-design.md) is unaffected and unblocked by this work — when it ships, V01_LOCK_AREAS/V01_WARDEN_ONLY can be retired independently of each other.