Skip to content

Drone System — Phase 3 (Drone Bay UI) Implementation Plan

Drone System — Phase 3 (Drone Bay UI) Implementation Plan

Section titled “Drone System — Phase 3 (Drone Bay UI) Implementation Plan”

For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development or executing-plans. Each task = one bh-dev-chunk cycle (TDD → --import → boot-check → full GUT suite → scripts/check-test-count.sh → determinism). Steps use - [ ].

Goal: Make the Phase-2 drone classes player-fieldable — a Drone Bay where you assign classes to your slots and deploy, persisted between runs.

Architecture: A MetaState.drone_loadout (slot → class id) persisted in the save; a render-side loadout builder turns it into the INPUT specs Sim.set_loadout() already consumes; a DroneBayPanel overlay (opened from the pause menu) edits the loadout; the drone button quick-deploys the saved loadout (the Phase-1 input.decoy edge already deploys _current_loadout() — we just keep set_loadout fed from MetaState). All render/meta-side → determinism untouched (drones are opt-in; the sim already has deploy_drones).

Tech Stack: Godot 4.6 typed GDScript; CanvasLayer UI; GUT 9.6. Branch/worktree: feat/drone-system at ~/Claude/bullet-heaven-drones.

  • Determinism byte-identical — survival 1405185210/3122397125, crystals 91572468/1173256610. Pure UI + meta + the existing set_loadout/deploy_drones seam. Re-check each task.
  • tvOS focus nav (debounced d-pad + JOY_BUTTON_A; not visible input guard) — mirror PauseMenu/MetaShopPanel.
  • Phase 4 (shop) is what UNLOCKS classes; for Phase 3, owns_class defaults all 5 available so the Bay is usable now (Phase 4 tightens it). Numbers/loadout shape are forward-compatible.
  • Don’t touch CLAUDE.md/git add -A; coordinate merges with the UI agent at the phase boundary.
  • sim/meta_state.gddrone_loadout: Array (slot→class), owns_class(), build_drone_loadout(), persistence.
  • ui/drone_bay_panel.gd (new) — the Bay overlay (slots × class cycler + Deploy).
  • ui/pause_menu.gd — a Drone Bay button (mirrors the Shop button) → bay_requested.
  • main.gd — open the Bay from pause; on Deploy, sim.set_loadout(meta.build_drone_loadout(...)) + trigger a deploy + unpause; keep set_loadout fed at run start so the drone button quick-deploys.
  • Tests: tests/test_drone_loadout.gd (meta), tests/test_drone_bay_panel.gd (UI), pause-menu test update.

Task P3.1: MetaState drones section (loadout + persistence + builder)

Section titled “Task P3.1: MetaState drones section (loadout + persistence + builder)”

Files: sim/meta_state.gd, tests/test_drone_loadout.gd (new). Interfaces — Produces:

  • var drone_loadout: Array = ["sentinel"] — slot→class id (“” = empty slot). Default a single Sentinel (Phase-1 parity).
  • owns_class(klass) -> bool — sentinel always; others via a future unlock-drone-<klass> level (default true in Phase 3 so the Bay is usable; Phase 4 adds the unlock entries + this gates on them).
  • build_drone_loadout(defs, sentinel_cfg) -> Array — maps each non-empty owned slot to a resolved spec: sentinel → the sentinel_cfg (from selected_decoy); others → {klass, life:1.0, dmg:1.0, radius:1.0, heal:0.0, extras:0} (default scales; the sim’s per-class consts are the base, Phase-4 shop raises scales).
  • to_dict/from_dict round-trip drone_loadout.
  • Test: default loadout is ["sentinel"]; round-trips through to_dict/from_dict; build_drone_loadout drops empty/unowned slots + maps sentinel to the cfg and others to default-scale specs; owns_class("sentinel") true.
  • Implement; full suite + determinism unchanged. Commit.

Files: ui/drone_bay_panel.gd (new), tests/test_drone_bay_panel.gd (new). Interfaces — Produces:

  • open_bay(meta, defs, max_slots) — shows max_slots slot rows; each row CYCLES through ["", sentinel, bomber, interceptor, disruptor, logistics] (only owned classes), seeded from meta.drone_loadout. A Deploy row at the end. signal deploy_requested (writes the edited loadout back to meta.drone_loadout
    • MetaStore.save_state + emits), signal closed (back without deploy).
  • tvOS focus nav (debounced; ui_accept/JOY_BUTTON_A cycles the focused slot or activates Deploy; ui_cancel/B closes); not visible input guard; layer above the pause menu.
  • Test: opening builds max_slots slot controls + a Deploy control; cycling a slot updates the staged loadout; Deploy writes it back to meta.drone_loadout (use a fresh MetaState — assert the array, do NOT exercise the real-save path). Boot-verify the panel renders.
  • Implement; suite + determinism unchanged. Commit.

Task P3.3: main wiring — open the Bay + deploy + quick-deploy

Section titled “Task P3.3: main wiring — open the Bay + deploy + quick-deploy”

Files: ui/pause_menu.gd (add a Drone Bay button → bay_requested), main.gd. Interfaces: PauseMenu gains signal bay_requested + a button (mirror the Shop button). main: create the persistent DroneBayPanel (like meta_shop); wire pause_menu.bay_requested → hide pause + open the Bay (run stays paused); the Bay’s deploy_requestedsim.set_loadout(meta.build_drone_loadout( defs, _sentinel_cfg())), deploy the drones now (drain charge + sim.deploy_drones(...) or set a pending “deploy on next tick”), then close the Bay + resume; closed → re-show pause. At run start, call sim.set_loadout(meta.build_drone_loadout(...)) so the drone button quick-deploys the saved loadout (replaces the Phase-1 single-Sentinel fallback). _shop_open()-style guard so main hotkeys don’t fire under the Bay.

  • Boot + playtest desktop: pause → Drone Bay → assign classes → Deploy → the chosen drones launch; the drone button re-deploys the saved loadout. Determinism unchanged. Commit.
  • Deferred (note it): the spec’s tap = deploy / hold = open Bay on the drone button (needs press- duration detection) — Phase 3 ships the pause-menu Bay entry + drone-button quick-deploy; tap/hold is a later input refinement. Targeting priority lists (auto vs per-type) also deferred — Phase 3 fields classes with auto targeting (policy 0); the priority-list UI is a follow-up.
  • Self-review (determinism, /sim untouched, save round-trip, tvOS nav). fetch + merge origin/main into the branch; FLAG Phase 3 ready for Chris to merge + deploy + playtest the fieldable classes. Phase 4 (shop trees: per-class unlock + attribute upgrades + +1 slot) gets its own plan — that’s what gives the classes their upgrade depth + gates ownership.
  • P3.1 (meta) and P3.2 (panel) are independent; P3.3 wires them into the run. All pure UI/meta/seam → the determinism baseline never moves. After Phase 3, the classes are player-fieldable with default stats; Phase 4 adds the shop depth, Phase 5 the counter-tuning.