Skip to content

v0.1 Warden-Only Boss + Post-Kill Teaser Wormhole Implementation Plan

v0.1 Warden-Only Boss + Post-Kill Teaser Wormhole Implementation Plan

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

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: For v0.1, survival only ever spawns the Warden boss; the first time it dies each run, a teaser wormhole opens that (when flown through) shows a full-screen “more awaits” overlay — a random locked boss + up to 3 existing enemies previewed against a spiral-galaxy backdrop — then resumes the same run untouched.

Architecture: A V01_WARDEN_ONLY flag short-circuits the survival boss picker to always spawn the Warden. A second, fully independent wormhole system (separate from the real area-warp system, which stays locked) spawns once per run on that Warden’s death, and on fly-through fills a one-shot teaser_event with a random boss + enemies drawn from two small config arrays (TEASER_BOSS_POOL/TEASER_ENEMY_POOL) built specifically so v0.2 can repoint them at v0.3’s content later. main.gd resolves the event into display values (colour/name/labels) and hands them to a new pure-presentation BossTeaserOverlay, which freezes the run the same way WeaponInfoOverlay/CodexOverlay already do.

Tech Stack: Godot 4.6 / typed GDScript, GUT 9.6.0 for tests.

  • Determinism baseline must stay byte-identical: snapshot_string().hash() = 2730172591, state_checksum() = 4075578713 (pinned in tests/test_determinism_checksum.gd + test_determinism_crystals.gd). Every new code path in this plan is gated well past the pinned 10s/600-tick window (the Warden itself never spawns before BOSS_FIRST_TIME = 210.0), so this must require zero re-pinning — treat any movement as a bug in the change, not an expected re-pin.
  • /sim stays pure logic: no Node/render/Input/Engine/Time APIs in sim/sim.gd changes. BossTeaserOverlay and all main.gd changes are render-side only.
  • No GUT test for BossTeaserOverlay — matches this project’s established convention (render/UI classes are verified by boot-check + playtest, not unit tests; see funzo_renderer.gd, boss2_renderer.gd, weapon_panel.gd for precedent).
  • Every new class_name file requires godot --headless --path . --import before the next boot-check or test run, or the stale global-class-cache silently drops its tests.
  • Boot-check after every task: godot --headless --path . --quit-after 90 2>&1 | grep "SCRIPT ERROR" must be empty. Never wrap with timeout (not on macOS PATH).
  • Full suite + count guard after every task: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit then bash scripts/check-test-count.sh — trust the script count, not just “all passed”.
  • No new art assets, no new bosses, no new enemies — everything teased already exists in full; this plan only builds the picking/presentation logic.
  • Commit at the end of each task, on this worktree’s branch only (worktree-warden-only-boss-teaser) — the user already approved this plan and chose subagent-driven execution, and the branch is isolated from main until an explicit merge step later, so per-task commits here don’t need a separate confirmation.

New files:

  • sim/... — no new sim files, all sim changes live in the existing sim/sim.gd (matches the codebase’s existing pattern of adding boss/wormhole logic directly there rather than splitting a single boss-picker concern into its own file).
  • ui/boss_teaser_overlay.gd — the new full-screen teaser overlay (BossTeaserOverlay extends CanvasLayer), self-contained, zero Sim/EnemyPool coupling.
  • tests/test_v01_warden_only.gd — new test file for the boss-lock.
  • tests/test_teaser_wormhole.gd — new test file for the teaser wormhole + event.

Modified files:

  • sim/sim.gd_maybe_spawn_survival_boss() gate, new teaser state/consts, _spawn_teaser_wormhole(), _update_teaser_wormhole(), consume_teaser_event(), two one-line call sites (_sweep_dead, tick).
  • main.gd — instantiate BossTeaserOverlay, add it to the physics freeze gate and the codex-encounter guard, feed the teaser wormhole into the existing WormholeRenderer, add _check_teaser_event() and call it from _process.

Files:

  • Modify: sim/sim.gd (the _maybe_spawn_survival_boss function, currently around line 2578, and its preceding line)
  • Test: tests/test_v01_warden_only.gd (new)

Interfaces:

  • Consumes: EnemyPool.TYPE_BOSS, Sim._boss_index(), Sim._spawn_boss(s), Sim.dev_clear_enemies(), Sim.BOSS_FIRST_TIME (all pre-existing).

  • Produces: Sim.V01_WARDEN_ONLY: bool (const, default true) — later tasks and any future session read this to know the v0.1 gate is active.

  • Step 1: Write the failing test

Create tests/test_v01_warden_only.gd:

extends GutTest
# v0.1 launch: survival only ever spawns the Warden. Flip Sim.V01_WARDEN_ONLY to false to
# restore the full 5-boss rotation post-launch — see
# docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md.
func _sim() -> Sim:
return Sim.new(1, SimContentFixture.db())
func test_v01_warden_only_defaults_on() -> void:
assert_true(Sim.V01_WARDEN_ONLY, "the v0.1 gate is on by default")
func test_survival_boss_never_spawns_other_boss_types() -> void:
var sim := _sim()
for k in range(20):
sim._boss_spawn_count = k # exercises every remainder of the old `% 5` rotation
sim.dev_clear_enemies()
sim.run_time = Sim.BOSS_FIRST_TIME
sim._next_boss_time = Sim.BOSS_FIRST_TIME
sim._maybe_spawn_survival_boss()
var bi := sim._boss_index()
assert_ne(bi, -1, "a boss spawned for _boss_spawn_count=%d" % k)
assert_eq(sim.enemies.type_id[bi], EnemyPool.TYPE_BOSS,
"_boss_spawn_count=%d must still spawn the Warden while V01_WARDEN_ONLY is on" % k)
  • Step 2: Run test to verify it fails

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gtest=res://tests/test_v01_warden_only.gd -gexit Expected: FAIL — Sim.V01_WARDEN_ONLY does not exist yet (parse/attribute error), or (if the const doesn’t exist) a compile error surfaces for the whole file. Either failure mode is acceptable proof the test exercises not-yet-written code — do not proceed until you see a genuine failure, not a false pass.

  • Step 3: Write minimal implementation

In sim/sim.gd, find:

func _maybe_spawn_survival_boss() -> void:
if story != null:
return
if run_time < _next_boss_time:
return
if _any_boss_alive(): # one boss at a time
return
var s := _boss_hp_scale() # later bosses spawn tougher (time-based)
var pick := _boss_spawn_count % 5
match pick:
0:
_spawn_boss(s)
1:
_spawn_boss2(player.pos + rng.rand_unit_dir() * 640.0, s)
2:
_spawn_funzo(player.pos + rng.rand_unit_dir() * 640.0, s)
3:
_spawn_graviton(player.pos + rng.rand_unit_dir() * 640.0, s)
4:
_spawn_eye(player.pos + rng.rand_unit_dir() * 640.0, s)

Replace with:

docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md.
# v0.1 launch: survival only ever spawns the Warden — none of the other 4 bosses. Flip to
# false to restore the full 5-boss rotation post-launch (Boss2/FunZo/Graviton/Eye code below
# is untouched, just unreachable while this is true). See
const V01_WARDEN_ONLY := true
func _maybe_spawn_survival_boss() -> void:
if story != null:
return
if run_time < _next_boss_time:
return
if _any_boss_alive(): # one boss at a time
return
var s := _boss_hp_scale() # later bosses spawn tougher (time-based)
if V01_WARDEN_ONLY:
_spawn_boss(s)
return
var pick := _boss_spawn_count % 5
match pick:
0:
_spawn_boss(s)
1:
_spawn_boss2(player.pos + rng.rand_unit_dir() * 640.0, s)
2:
_spawn_funzo(player.pos + rng.rand_unit_dir() * 640.0, s)
3:
_spawn_graviton(player.pos + rng.rand_unit_dir() * 640.0, s)
4:
_spawn_eye(player.pos + rng.rand_unit_dir() * 640.0, s)
  • Step 4: Run test to verify it passes

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit Expected: PASS (both new tests green; total test count is prior count + 2).

  • Step 5: Boot-check + count guard

Run: godot --headless --path . --quit-after 90 2>&1 | grep "SCRIPT ERROR" — expect empty output. Run: bash scripts/check-test-count.sh — expect test-count guard OK with the new file counted.

  • Step 6: Determinism check

Run the full suite again (it includes tests/test_determinism_checksum.gd and test_determinism_crystals.gd) — both must still report the pinned values 2730172591 / 4075578713. This task changes only code paths that run at/after BOSS_FIRST_TIME = 210.0, far outside the pinned 10s window, so zero movement is expected. If either test fails, stop and investigate before continuing — do not re-pin to make it pass.

  • Step 7: Commit
Terminal window
git add sim/sim.gd tests/test_v01_warden_only.gd
git commit -m "feat(sim): v0.1 survival boss lock — Warden only
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Task 2: Teaser wormhole spawn + once-per-run latch

Section titled “Task 2: Teaser wormhole spawn + once-per-run latch”

Files:

  • Modify: sim/sim.gd (new vars near var wormholes: Array = [] at line ~508; new function near _spawn_area_wormhole at line ~3970; one call site inside _sweep_dead at line ~3917-3918)
  • Test: tests/test_teaser_wormhole.gd (new — this task adds to it; Task 3 extends the same file)

Interfaces:

  • Consumes: Sim.areas_enabled, Sim.wormholes, Sim._spawn_area_wormhole(pos), Sim.WORMHOLE_RADIUS, Sim._sweep_dead(), Sim.V01_WARDEN_ONLY (from Task 1).

  • Produces: Sim.teaser_wormhole: Dictionary ({"pos": Vector2} when active, {} otherwise) and Sim._spawn_teaser_wormhole(pos: Vector2) -> void — Task 3’s _update_teaser_wormhole() reads/clears teaser_wormhole.

  • Step 1: Write the failing test

Create tests/test_teaser_wormhole.gd:

extends GutTest
# v0.1 teaser wormhole: fully independent of the real area-warp system (Sim.wormholes /
# Sim.areas_enabled / Sim.enter_area), spawned once per run on the first Warden kill. See
# docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md.
func _sim() -> Sim:
return Sim.new(1, SimContentFixture.db())
func _kill_warden(sim: Sim) -> void:
sim.run_time = Sim.BOSS_FIRST_TIME
sim._spawn_boss()
var bi := sim._boss_index()
sim.enemies.data[bi] = 0.0
sim._sweep_dead()
func test_warden_kill_spawns_exactly_one_teaser_wormhole() -> void:
var sim := _sim()
_kill_warden(sim)
assert_eq(sim.teaser_wormhole.size(), 1, "teaser_wormhole holds one key (pos)")
assert_true(sim.teaser_wormhole.has("pos"))
func test_real_wormhole_system_stays_independent() -> void:
var sim := _sim()
sim.areas_enabled = false # what main.gd sets for v0.1 (V01_LOCK_AREAS)
_kill_warden(sim)
assert_true(sim.wormholes.is_empty(), "the real area-warp wormhole stays locked")
assert_eq(sim.teaser_wormhole.size(), 1, "the teaser wormhole still spawns independently")
func test_second_warden_kill_same_run_does_not_spawn_another() -> void:
var sim := _sim()
_kill_warden(sim)
assert_eq(sim.teaser_wormhole.size(), 1, "first kill spawns it")
sim.teaser_wormhole = {} # simulate it having been consumed by fly-through (Task 3 covers the real path)
_kill_warden(sim) # _spawn_boss() bypasses the run_time-gated picker, so this just spawns+kills another Warden directly
assert_true(sim.teaser_wormhole.is_empty(),
"the once-per-run latch blocks a second teaser wormhole even though the slot is free again")
  • Step 2: Run test to verify it fails

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gtest=res://tests/test_teaser_wormhole.gd -gexit Expected: FAIL — sim.teaser_wormhole does not exist yet.

  • Step 3: Write minimal implementation

In sim/sim.gd, find:

var areas_enabled: bool = true
var wormholes: Array = [] # {pos, dest}; ≤1 at a time, spawned on a boss kill (Task 2)

Add immediately after:

# v0.1 teaser wormhole — a system fully separate from the real area-warp one above (see
# docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md). Never touches
# wormholes/areas_enabled/enter_area. Repointable at new content for future versions by
# editing TEASER_BOSS_POOL/TEASER_ENEMY_POOL only (Task 3).
var teaser_wormhole: Dictionary = {} # {pos} when active, {} otherwise
var _teaser_shown_this_run: bool = false

Then find (near the end of _sweep_dead):

i -= 1
if boss_died_at != Vector2.INF:
_spawn_area_wormhole(boss_died_at) # a boss fell → open the gateway to the next area

Replace with:

i -= 1
if boss_died_at != Vector2.INF:
_spawn_area_wormhole(boss_died_at) # a boss fell → open the gateway to the next area
_spawn_teaser_wormhole(boss_died_at) # v0.1: a "more awaits" teaser, independent of the above

Then find:

# Spawn the area-gateway wormhole at a boss's death spot — ≤1 at a time, destination = the other area.
# Boss deaths only happen long after the determinism baseline window, so this never runs in it.
func _spawn_area_wormhole(pos: Vector2) -> void:
if not areas_enabled:
return # v0.1: the explorable-areas system is gated off
if not wormholes.is_empty():
return
wormholes.append({"pos": pos, "dest": other_area()})

Add immediately after (before _update_wormholes):

# v0.1 teaser wormhole (see the TEASER_* consts in Task 3) — independent of
# _spawn_area_wormhole above. Once per run: the first Warden kill spawns it, later Warden
# kills that run do not (the run's own Sim instance resets _teaser_shown_this_run each run).
func _spawn_teaser_wormhole(pos: Vector2) -> void:
if not V01_WARDEN_ONLY or _teaser_shown_this_run or not teaser_wormhole.is_empty():
return
_teaser_shown_this_run = true
teaser_wormhole = {"pos": pos}
  • Step 4: Run test to verify it passes

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit Expected: PASS.

  • Step 5: Boot-check + count guard (same commands as Task 1, Step 5)

  • Step 6: Determinism check (same as Task 1, Step 6 — zero movement expected)

  • Step 7: Commit

Terminal window
git add sim/sim.gd tests/test_teaser_wormhole.gd
git commit -m "feat(sim): v0.1 teaser wormhole spawn (once per run, independent of area-warp)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Task 3: Teaser pools + fly-through detection + one-shot event

Section titled “Task 3: Teaser pools + fly-through detection + one-shot event”

Files:

  • Modify: sim/sim.gd (new consts near the Task 2 vars; new functions near _update_wormholes at line ~3979; one call site inside tick() at line ~886)
  • Test: tests/test_teaser_wormhole.gd (extend from Task 2)

Interfaces:

  • Consumes: Sim.teaser_wormhole (Task 2), Sim.WORMHOLE_RADIUS, Sim.rng, Sim.tick().

  • Produces: Sim.TEASER_BOSS_POOL: Array[int], Sim.TEASER_ENEMY_POOL: Array[String], Sim.teaser_event: Dictionary ({"boss_type": int, "enemy_ids": Array[String]}), Sim.consume_teaser_event() -> Dictionary — Task 5’s main._check_teaser_event() calls this.

  • Step 1: Write the failing test

Append to tests/test_teaser_wormhole.gd:

func _fly_through_teaser(sim: Sim) -> void:
sim.player.pos = sim.teaser_wormhole["pos"]
sim._update_teaser_wormhole()
func test_flying_through_teaser_wormhole_produces_event_from_pools() -> void:
var sim := _sim()
_kill_warden(sim)
_fly_through_teaser(sim)
assert_true(sim.teaser_wormhole.is_empty(), "the wormhole is consumed on fly-through")
assert_true(sim.teaser_event.has("boss_type"))
assert_true(Sim.TEASER_BOSS_POOL.has(int(sim.teaser_event["boss_type"])),
"the teased boss is drawn from TEASER_BOSS_POOL")
var enemy_ids: Array = sim.teaser_event["enemy_ids"]
assert_lte(enemy_ids.size(), 3, "at most 3 enemies teased")
var seen := {}
for id in enemy_ids:
assert_true(Sim.TEASER_ENEMY_POOL.has(String(id)),
"each teased enemy id is drawn from TEASER_ENEMY_POOL")
assert_false(seen.has(id), "no duplicate enemy ids in one teaser")
seen[id] = true
func test_consume_teaser_event_returns_once_then_empty() -> void:
var sim := _sim()
_kill_warden(sim)
_fly_through_teaser(sim)
var ev := sim.consume_teaser_event()
assert_true(ev.has("boss_type"), "first consume returns the event")
var ev2 := sim.consume_teaser_event()
assert_true(ev2.is_empty(), "second consume reads empty (already cleared)")
  • Step 2: Run test to verify it fails

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gtest=res://tests/test_teaser_wormhole.gd -gexit Expected: FAIL — Sim.TEASER_BOSS_POOL/_update_teaser_wormhole/consume_teaser_event do not exist yet.

  • Step 3: Write minimal implementation

In sim/sim.gd, extend the block added in Task 2:

# v0.1 teaser wormhole — a system fully separate from the real area-warp one above (see
# docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md). Never touches
# wormholes/areas_enabled/enter_area. Repointable at new content for future versions by
# editing TEASER_BOSS_POOL/TEASER_ENEMY_POOL only.
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"]
const TEASER_ENEMY_SAMPLE: int = 3 # up to this many enemies previewed per teaser
var teaser_wormhole: Dictionary = {} # {pos} when active, {} otherwise
var _teaser_shown_this_run: bool = false
var teaser_event: Dictionary = {} # {boss_type, enemy_ids} once fly-through fires; one-shot

(This replaces the smaller block Task 2 added — same vars, with the three new consts and teaser_event added alongside.)

Then find:

# Player flies over the wormhole → emit a warp event (render orchestrates the transition + enter_area)
# and consume it. Iterates an empty array in the baseline → no-op.
func _update_wormholes() -> void:
var r := WORMHOLE_RADIUS + player.radius
for w in wormholes:
if player.pos.distance_squared_to(w["pos"]) <= r * r:
area_events.append({"kind": "warp", "dest": String(w["dest"])})
wormholes.clear()
return

Add immediately after:

# Player flies over the TEASER wormhole → pick a random locked boss + up to
# TEASER_ENEMY_SAMPLE distinct enemies from the pools, fill teaser_event, clear the wormhole.
# Draws from `rng` (a spawn-flavor pick, not an upgrade choice — never desyncs the upgrade
# stream). Never runs in the baseline window: the Warden itself never spawns before
# BOSS_FIRST_TIME (210s), so teaser_wormhole is always empty inside the pinned 10s window.
func _update_teaser_wormhole() -> void:
if teaser_wormhole.is_empty():
return
var r := WORMHOLE_RADIUS + player.radius
var pos: Vector2 = teaser_wormhole["pos"]
if player.pos.distance_squared_to(pos) > r * r:
return
var boss_type: int = TEASER_BOSS_POOL[rng.randi_range(0, TEASER_BOSS_POOL.size() - 1)]
var pool: Array[String] = TEASER_ENEMY_POOL.duplicate()
for i in range(pool.size()):
var j := rng.randi_range(i, pool.size() - 1)
var tmp := pool[i]
pool[i] = pool[j]
pool[j] = tmp
var enemy_ids: Array[String] = []
for i in range(mini(TEASER_ENEMY_SAMPLE, pool.size())):
enemy_ids.append(pool[i])
teaser_event = {"boss_type": boss_type, "enemy_ids": enemy_ids}
teaser_wormhole = {}
# Render calls this once when it actually shows the overlay — a dropped frame (another
# overlay is up) doesn't lose the event, it just stays queued until read.
func consume_teaser_event() -> Dictionary:
var ev := teaser_event
teaser_event = {}
return ev

Then find, inside tick():

_update_wormholes() # fly over a boss-spawned wormhole → emit a warp event

Replace with:

_update_wormholes() # fly over a boss-spawned wormhole → emit a warp event
_update_teaser_wormhole() # v0.1: fly over the teaser wormhole → emit teaser_event
  • Step 4: Run test to verify it passes

Run: godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit Expected: PASS.

  • Step 5: Boot-check + count guard (same commands as Task 1, Step 5)

  • Step 6: Determinism check (same as Task 1, Step 6 — zero movement expected; _update_teaser_wormhole only draws from rng when teaser_wormhole is non-empty, which never happens before BOSS_FIRST_TIME)

  • Step 7: Commit

Terminal window
git add sim/sim.gd tests/test_teaser_wormhole.gd
git commit -m "feat(sim): teaser wormhole fly-through event + config-driven pools
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Files:

  • Create: ui/boss_teaser_overlay.gd

Interfaces:

  • Consumes: NeonTheme.title_font(), NeonTheme.mono_font() (existing).
  • Produces: BossTeaserOverlay.show_for(boss_color: Color, boss_name: String, enemy_labels: Array) -> void, BossTeaserOverlay.is_open() -> bool — Task 5’s main.gd calls both.

This class takes already-resolved presentation values, never a raw Sim/EnemyPool reference, so it stays reusable for a v0.2 teaser with different content. No GUT test (matches this project’s established convention for render/UI classes — see Global Constraints).

  • Step 1: Write the file

Create ui/boss_teaser_overlay.gd:

class_name BossTeaserOverlay
extends CanvasLayer
# v0.1 "more awaits" teaser: a full-screen takeover shown once per run, the first time the
# player flies through the post-Warden-kill teaser wormhole. Pure presentation — takes
# already-resolved values (colour/name/labels), never touches Sim/EnemyPool directly, so it
# stays trivially reusable for a v0.2 teaser pointed at different content. Freezes the run via
# is_open() (main.gd adds it to the _physics_process gate, same idiom as WeaponInfoOverlay).
# See docs/superpowers/specs/2026-07-01-warden-only-boss-teaser-design.md.
const AUTO_DISMISS_S: float = 6.0
# Procedural spiral-galaxy backdrop + boss glow — additive vector art, same idiom as
# funzo_renderer.gd/boss2_renderer.gd. No image assets.
class _GalaxyBackdrop extends Node2D:
var _t := 0.0
var _radius := 420.0
var glow_color := Color(1.0, 0.3, 0.3)
func _init() -> void:
var mat := CanvasItemMaterial.new()
mat.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD
material = mat
func _process(dt: float) -> void:
_t += dt
queue_redraw()
func _draw() -> void:
var arms := 4
for arm in range(arms):
var base_a := _t * 0.08 + float(arm) * TAU / float(arms)
var pts := PackedVector2Array()
var steps := 64
for s in range(steps):
var t := float(s) / float(steps - 1)
var a := base_a + t * TAU * 1.4
var rr := t * _radius
pts.append(Vector2(cos(a), sin(a)) * rr)
var col := Color(0.55, 0.35, 1.0, 0.22) if arm % 2 == 0 else Color(0.35, 0.65, 1.0, 0.16)
draw_polyline(pts, col, 26.0, true)
draw_circle(Vector2.ZERO, 90.0, Color(glow_color.r, glow_color.g, glow_color.b, 0.14))
draw_circle(Vector2.ZERO, 55.0, Color(glow_color.r, glow_color.g, glow_color.b, 0.55))
draw_circle(Vector2.ZERO, 30.0, Color(1.0, 1.0, 1.0, 0.85).lerp(glow_color, 0.3))
var _open := false
var _timer := 0.0
var _backdrop: _GalaxyBackdrop
var _title: Label
var _boss_name: Label
var _enemy_row: HBoxContainer
func _ready() -> void:
layer = 23 # above WeaponInfoOverlay (22) — this is a rarer, higher-priority takeover
visible = false
var dim := ColorRect.new()
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.color = Color(0.01, 0.0, 0.03, 0.94)
dim.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(dim)
_backdrop = _GalaxyBackdrop.new()
add_child(_backdrop)
_title = Label.new()
_title.text = "MORE AWAITS"
_title.size = Vector2(400, 40)
_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_title.add_theme_font_override("font", NeonTheme.title_font())
_title.add_theme_font_size_override("font_size", 26)
_title.add_theme_color_override("font_color", Color(0.75, 0.9, 1.0))
add_child(_title)
_boss_name = Label.new()
_boss_name.size = Vector2(400, 30)
_boss_name.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_boss_name.add_theme_font_override("font", NeonTheme.title_font())
_boss_name.add_theme_font_size_override("font_size", 22)
_boss_name.add_theme_color_override("font_color", Color(1.0, 0.95, 0.98))
add_child(_boss_name)
_enemy_row = HBoxContainer.new()
_enemy_row.add_theme_constant_override("separation", 24)
add_child(_enemy_row)
func is_open() -> bool:
return _open
func show_for(boss_color: Color, boss_name: String, enemy_labels: Array) -> void:
_open = true
visible = true
_timer = AUTO_DISMISS_S
var vp := get_viewport().get_visible_rect().size
var center := vp * 0.5
_backdrop.position = center
_backdrop.glow_color = boss_color
_title.position = Vector2(center.x - 200.0, 60.0)
_boss_name.text = boss_name.to_upper()
_boss_name.position = Vector2(center.x - 200.0, center.y + 100.0)
for c in _enemy_row.get_children():
c.queue_free()
for label_text in enemy_labels:
var lbl := Label.new()
lbl.text = String(label_text).to_upper()
lbl.add_theme_font_override("font", NeonTheme.mono_font())
lbl.add_theme_font_size_override("font_size", 14)
lbl.add_theme_color_override("font_color", Color(0.65, 0.85, 1.0))
_enemy_row.add_child(lbl)
_enemy_row.position = Vector2(center.x - 200.0, vp.y - 90.0)
func _close() -> void:
_open = false
visible = false
func _process(delta: float) -> void:
if not _open:
return
_timer -= delta
if _timer <= 0.0:
_close()
func _input(event: InputEvent) -> void:
if not _open:
return
var confirm := event.is_action_pressed("ui_accept") \
or (event is InputEventJoypadButton and event.pressed and event.button_index == JOY_BUTTON_A)
if confirm:
_close()
get_viewport().set_input_as_handled()
  • Step 2: Re-import (new class_name file)

Run: godot --headless --path . --import Expected: completes without error; this refreshes the global class cache so BossTeaserOverlay is registered.

  • Step 3: Boot-check

Run: godot --headless --path . --quit-after 90 2>&1 | grep "SCRIPT ERROR" Expected: empty (this file isn’t instantiated by anything yet, so this mainly confirms it parses cleanly; Task 5 is where it’s actually wired in and exercised).

  • Step 4: Full suite + count guard (same commands as Task 1, Step 5 — the new file adds no tests of its own per the Global Constraints note, so the test count is unchanged from Task 3’s).

  • Step 5: Commit

Terminal window
git add ui/boss_teaser_overlay.gd
git commit -m "feat(ui): BossTeaserOverlay — v0.1 full-screen 'more awaits' takeover
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Files:

  • Modify: main.gd (new member var near var weapon_info: WeaponInfoOverlay; instantiation near where weapon_info = WeaponInfoOverlay.new() runs; the _physics_process freeze-gate line; the wormhole_renderer.update_wormholes(...) call; the _check_codex_encounters() guard; a new _check_teaser_event() function + its call site in _process)

Interfaces:

  • Consumes: Sim.teaser_wormhole, Sim.teaser_event, Sim.consume_teaser_event() (Tasks 2-3), BossTeaserOverlay.show_for(...)/.is_open() (Task 4), main._base_color_for_type(tid, el) (existing, added earlier this session for enemy-projectile colouring), main.codex_db (existing BestiaryDB), WormholeRenderer.update_wormholes(Array) (existing, unchanged signature).
  • Produces: nothing new consumed by later tasks — this is the integration point.

No GUT test for this task (it’s all main.gd/render wiring — matches the established convention). Verified by boot-check + an extended headless smoke run.

  • Step 1: Declare the member variable

In main.gd, find:

var weapon_info: WeaponInfoOverlay # in-play weapon details (Y button / V), freezes the sim while open

Add immediately after:

var boss_teaser: BossTeaserOverlay # v0.1 "more awaits" full-screen takeover, freezes the sim while open
  • Step 2: Instantiate it

In main.gd, find:

weapon_info = WeaponInfoOverlay.new()
add_child(weapon_info)

Add immediately after:

boss_teaser = BossTeaserOverlay.new()
add_child(boss_teaser)
  • Step 3: Add it to the physics freeze gate

In main.gd, find:

if sim.game_over or _paused_for_levelup or _story_won or _paused_for_menu \
or weapon_info.is_open() or codex.is_open() or _warping:
return

Replace with:

if sim.game_over or _paused_for_levelup or _story_won or _paused_for_menu \
or weapon_info.is_open() or codex.is_open() or boss_teaser.is_open() or _warping:
return
  • Step 4: Feed the teaser wormhole into the existing renderer

In main.gd, find:

wormhole_renderer.update_wormholes(sim.wormholes)

Replace with:

var wormhole_list := sim.wormholes.duplicate()
if not sim.teaser_wormhole.is_empty():
wormhole_list.append(sim.teaser_wormhole)
wormhole_renderer.update_wormholes(wormhole_list)
  • Step 5: Guard the codex first-encounter check against a stacked overlay

In main.gd, find (inside _check_codex_encounters):

if sim == null or sim.game_over or _paused_for_levelup or _paused_for_menu \
or _story_won or weapon_info.is_open() or codex.is_open() or meta == null:
return

Replace with:

if sim == null or sim.game_over or _paused_for_levelup or _paused_for_menu \
or _story_won or weapon_info.is_open() or codex.is_open() or boss_teaser.is_open() \
or meta == null:
return
  • Step 6: Add the check-and-show function

In main.gd, find _check_codex_encounters (around line 1320) and add this new function immediately after it:

# v0.1: the first time the player flies through the post-Warden teaser wormhole, show the
# full-screen "more awaits" overlay. Won't fire while another overlay is up (mirrors
# _check_codex_encounters' stacking guard above); the sim-side event survives until then.
func _check_teaser_event() -> void:
if sim == null or sim.game_over or _paused_for_levelup or _paused_for_menu \
or _story_won or weapon_info.is_open() or codex.is_open() or boss_teaser.is_open():
return
if sim.teaser_event.is_empty():
return
var event := sim.consume_teaser_event()
var boss_type := int(event.get("boss_type", -1))
var boss_color := _base_color_for_type(boss_type, -1)
var boss_name := String(codex_db.entry_for_type(boss_type).get("name", "???"))
var enemy_labels: Array = []
for id in event.get("enemy_ids", []):
enemy_labels.append(String(codex_db.entry_for_key(String(id)).get("name", String(id))))
boss_teaser.show_for(boss_color, boss_name, enemy_labels)
  • Step 7: Call it from _process

In main.gd, find:

_check_codex_encounters()

Replace with:

_check_codex_encounters()
_check_teaser_event()
  • Step 8: Boot-check

Run: godot --headless --path . --quit-after 90 2>&1 | grep "SCRIPT ERROR" Expected: empty.

  • Step 9: Full suite + count guard (same commands as Task 1, Step 5 — no new tests added this task, count unchanged from Task 3).

  • Step 10: Determinism check (same as Task 1, Step 6 — this task is 100% render-side main.gd wiring, zero /sim changes, so zero movement is expected).

  • Step 11: Extended headless smoke

Run: godot --headless --path . --quit-after 3600 2>&1 | grep -iE "SCRIPT ERROR|error|Invalid|Nonexistent|out of bounds|Index" | sort -u Expected: empty. This won’t reach BOSS_FIRST_TIME (210s), so it doesn’t exercise the teaser path itself (that’s covered by Tasks 1-3’s unit tests) — it confirms the new main.gd wiring (the extra .duplicate()/.append() per frame, the new guard conditions) doesn’t break normal play.

  • Step 12: Commit
Terminal window
git add main.gd
git commit -m "feat(main): wire BossTeaserOverlay into the run loop
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Task 6: Final verification + hand-off note

Section titled “Task 6: Final verification + hand-off note”

Files: none (verification only).

  • Step 1: Full ritual, one more time, on the whole feature together
Terminal window
godot --headless --path . --import
godot --headless --path . --quit-after 90 2>&1 | grep "SCRIPT ERROR"
godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gdir=res://tests -ginclude_subdirs -gexit
bash scripts/check-test-count.sh

Expected: all green, SCRIPT ERROR grep empty, test count matches tests/test_*.gd file count exactly (prior count + 4: 2 in test_v01_warden_only.gd, 4 in the extended test_teaser_wormhole.gd… i.e. whatever the actual new total is — the script asserts this automatically, just confirm it reports OK).

  • Step 2: Determinism, one more time

Confirm tests/test_determinism_checksum.gd and test_determinism_crystals.gd both still report 2730172591 / 4075578713 — zero movement across the whole feature.

  • Step 3: Note what’s NOT covered by this plan

This plan does not include: bumping Sim_Const.BUILD, committing a docs(claude.md) catch-up entry, syncing to the tvOS repo, or building/installing to the Apple TV. Those follow the same bh-deploy ritual used throughout this session (sync the changed files — sim/sim.gd, main.gd, ui/boss_teaser_overlay.gd, and the two new/extended test files — plus the whole tests/ dir, verify independently there, export/build/install) and should only happen once the user explicitly asks for the commit + deploy, matching every prior chunk this session.


Spec coverage:

  • Decision 1 (Warden-only + flag) → Task 1. ✓
  • Decision 2 (separate teaser system, real area system untouched) → Tasks 2-3 (explicitly tested in test_real_wormhole_system_stays_independent). ✓
  • Decision 3 (once per run) → Task 2 (test_second_warden_kill_same_run_does_not_spawn_another). ✓
  • Decision 4 (random pick, config-driven pools) → Task 3 (TEASER_BOSS_POOL/TEASER_ENEMY_POOL, tested for pool-membership + no-duplicates). ✓
  • Decision 5 (reuses existing content, no new enemies/bosses) → Tasks 3-5 use only existing EnemyPool.TYPE_* ids and existing bestiary keys. ✓
  • Decision 6 (full-screen takeover layout) → Task 4. ✓
  • Decision 7 (pauses the run, auto-dismiss/confirm-dismiss) → Tasks 4-5 (is_open()/freeze gate, AUTO_DISMISS_S timer + ui_accept/JOY_BUTTON_A). ✓
  • Load-bearing determinism constraint → re-checked at the end of every task, not just once. ✓
  • Data-flow diagram → matches Tasks 2, 3, 5 exactly (spawn → fly-through → event → consume → resolve → show_for → dismiss → resume). ✓
  • Scope guard (no new art/bosses/enemies, no persistence of “which boss was teased”, no skip-tracking, real area system untouched, no new audio) → honored throughout; nothing in this plan adds any of those. ✓

Placeholder scan: no TBD/TODO; every step has complete, runnable code; no “similar to Task N” — Task 3 explicitly re-states the full var block rather than pointing back to Task 2.

Type consistency: teaser_wormhole: Dictionary / teaser_event: Dictionary / TEASER_BOSS_POOL: Array[int] / TEASER_ENEMY_POOL: Array[String] are declared once (Tasks 2-3) and used with the same names/types in Task 5’s main.gd wiring and both test files. BossTeaserOverlay.show_for(boss_color: Color, boss_name: String, enemy_labels: Array) matches its Task 5 call site exactly (positional args, same order, same resolved types).