Bullet Heaven — Phase 1 Design (Singleplayer Core)
Bullet Heaven — Phase 1 Design (Singleplayer Core)
Section titled “Bullet Heaven — Phase 1 Design (Singleplayer Core)”Date: 2026-06-21 Status: Approved (design) — pending spec review Engine: Godot 4.6.3 stable Language: Typed GDScript (with an isolated hot loop swappable to C#/GDExtension if profiling demands)
1. Vision
Section titled “1. Vision”A neon-abstract “bullet heaven” survival game in the lineage of Vampire Survivors and Nova Drift. Direct, accessible movement (one analog stick) combined with deep, transformative build-craft (Nova Drift DNA). Cute/neon abstract art, rendered almost entirely in code (glowing shapes + particles) — no sprite-artist dependency, lightest possible asset load, gorgeous fast.
Target platforms (whole project)
Section titled “Target platforms (whole project)”iOS, Android, macOS, Windows (Linux/Web essentially free from Godot export).
The three pillars
Section titled “The three pillars”- Accessible to control — one-thumb playable on a phone.
- Deep to build — upgrade choices radically change how you play, not just stat bumps.
- High performance — thousands of entities on screen at a high, stable frame rate.
Phasing (whole project)
Section titled “Phasing (whole project)”- Phase 1 (this spec): complete, fun, singleplayer timed-survival game.
- Phase 2: content & depth — many weapons/abilities/enemies, synergies, meta-progression.
- Phase 3: co-op multiplayer (2–32 players), layered onto the working game.
Phase 1 is built multiplayer-aware (see §7) so Phase 3 is a layer, not a rewrite. No networking is implemented in Phase 1.
2. Architecture
Section titled “2. Architecture”2.1 One-way data flow
Section titled “2.1 One-way data flow”Input (per-player InputState) ──► Simulation (fixed deterministic tick) ──► Render (reads sim state)- Simulation owns all gameplay state and never touches the screen. Runs on a fixed
timestep via
_physics_process, with the tick rate set throughEngine.physics_ticks_per_second(target 60 Hz). The sim integrates using a constant dt (e.g.1.0/60.0), NOT thedeltaargument, so it is bit-reproducible — the precondition for future netcode. - Render reads sim state each frame and draws it. Never mutates gameplay. Free to run at the display refresh rate; interpolates between sim ticks for smoothness.
- Input is abstracted into a per-player
InputStatestruct. A local player and a (future) remote player are just two input sources feeding identical sim slots.
2.2 Entity strategy (Approach C — hybrid)
Section titled “2.2 Entity strategy (Approach C — hybrid)”- Node-based entities for the few important objects: player, bosses, pickups that need rich behaviour, UI. Editor-friendly, low count.
- Data-oriented swarm for the many cheap objects: basic enemies, bullets/projectiles,
XP gems. Stored in flat arrays, rendered via
MultiMeshInstance2D(one draw call for thousands), collision via a hand-rolled uniform-grid spatial hash. Deterministic and serializable. Built data-oriented from the first enemy — never retrofitted.
2.3 Rendering specifics (Godot 4.6)
Section titled “2.3 Rendering specifics (Godot 4.6)”- Swarm:
MultiMeshInstance2Dwithuse_colors+use_custom_data. Per-instancetransform_2d(position/rotation/scale),color(tint/team), andcustom_datafeeding a small shader for per-entity effects (hit-flash, glow pulse, spawn-in). - Neon look: a
WorldEnvironmentwith glow enabled (4.6 blends glow pre-tonemap, “Screen” default) for the bloom. Shapes drawn as simple meshes/polygons. - Juice:
GPUParticles2Dfor bursts (deaths, level-up, hits). Render-only;use_fixed_seedavailable if we ever want deterministic visuals. Particles never feed the sim.
2.4 Project layout
Section titled “2.4 Project layout”/sim # pure logic: entity arrays, spatial hash, spawn director, weapons, mods, XP, RNG, run state/render # MultiMesh swarm renderer, node entities, camera, particles, glow/environment/input # input sources (touch joystick, keyboard, gamepad) → InputState/ui # HUD, level-up upgrade picker, results screen, menus/data # weapon/enemy/mod/evolution definitions (Resources/.tres, data-driven)/tests # GUT unit tests for /sim (headless)2.5 Determinism rules (cheap now, essential later)
Section titled “2.5 Determinism rules (cheap now, essential later)”- Single seeded RNG owned by the sim; all randomness flows through it.
- Constant dt integration (above).
- No engine physics for the swarm (engine physics is non-deterministic cross-machine).
- Sim state is plain serializable data (arrays + structs) — snapshot/diff friendly.
3. Core gameplay loop
Section titled “3. Core gameplay loop”- Move with the analog stick (360°). Weapons auto-fire on cooldown and auto-target (nearest enemy, or per-weapon rule).
- Enemies spawn continuously from off-screen, driven by the spawn director which escalates on a timeline (count, type mix, toughness).
- Kill enemies → XP gems drop. Walk within pickup radius to collect.
- Fill XP bar → level up. Sim pauses; the upgrade picker offers 3–4 choices; pick one; resume.
- Difficulty ramps on the timeline; mini-bosses/elites at intervals; a final boss near the end of the timed run (~20 min target).
- Run ends on death or surviving the timer → results screen (time, level, kills, build summary).
Endless mode (Phase 2-ish, designed-for now): same loop, end-cap removed, escalation curve continues. Costs ~nothing to keep possible because the spawn director and run state are already parameterised by time.
Controls
Section titled “Controls”- Movement: virtual joystick (touch), WASD/arrows (keyboard), left stick (gamepad). 360° analog.
- Attacks: fully automatic — auto-fire + auto-target. One-thumb playable.
4. Build-craft system (the deep pillar)
Section titled “4. Build-craft system (the deep pillar)”All upgrades flow through the same level-up picker. Three layers:
- Weapons (active). Each auto-fires with its own behaviour; levels up independently (1→max). Player starts with one, acquires more.
- Mods (passive). Two deliberately-mixed flavours:
- Stat mods: fire rate, damage, projectile count, area, move speed, pickup radius, max HP, crit. The reliable backbone.
- Transformative mods: change how a weapon behaves — pierce, split-on-hit, exploding orbits, chaining beams. These create builds.
- Weapon evolutions. At max weapon level + a required mod, a weapon evolves into a dramatically transformed version. Rewards committing to a path; creates the “these combine!” discovery moment.
Data-driven: weapons, mods, enemies, and evolutions are defined as Resources in /data.
Adding/tuning content = editing data, not code. Upgrade application logic lives in /sim
and is unit-tested.
5. Phase 1 vertical slice (content scope)
Section titled “5. Phase 1 vertical slice (content scope)”A complete, shippable singleplayer game — a focused slice, not the full content vision.
| Element | Phase 1 | Notes |
|---|---|---|
| Arena | 1 large bounded neon field | scrolling grid bg, camera follows player, glow/bloom |
| Player character | 1 | neon ship/shape, one starting weapon |
| Weapons | 5 | Pulse (homing shot), Orbit (orbiting shards), Beam (sweeping laser), Nova (AoE pulse), Turret/Mine |
| Mods | ~10 | ~6 stat + ~4 transformative |
| Evolutions | 2–3 | prove the evolve hook |
| Enemy types | 5 | Swarmer (fast/weak), Tank (slow/tough), Shooter (ranged), Splitter (splits on death), Elite/mini-boss |
| Boss | 1 | final boss at end of timed run |
| Run length | ~20 min timed | spawn director escalates across it |
| Meta-progression | none | deferred to Phase 2 to keep the slice focused |
Performance target: stable 60 FPS on desktop with several thousand simultaneous entities (enemies + bullets + gems); mobile verified to hold a smooth frame rate at the densities a 20-minute run reaches.
6. Tooling, testing & workflow
Section titled “6. Tooling, testing & workflow”- Godot 4.6.3 installed (CLI at
/opt/homebrew/bin/godot, editor at/Applications/Godot.app). - I edit
.gd/.tscn/.tresas text directly; Chris uses the editor for visual verification and any scene wiring easier in the GUI. - Live docs: context7 MCP for current Godot 4.6 API (never code from stale memory).
- Testing:
/simis pure logic → unit-tested headlessly with GUT viagodot --headless. TDD the sim: spawn director, collision/damage, XP/leveling, mod & evolution application, RNG determinism. Render/feel verified by playing. - Runtime self-verification (added later, vetted): when the render phase begins, add a zero-footprint Godot runtime MCP (e.g. Erodenn/godot-mcp-runtime or satelliteoflove/godot-mcp) so the agent can screenshot, inject input, and read live game state to self-verify visuals/feel. Security-vetted before install; not needed for the headless sim work, so not installed up front.
- Repo:
bullet-heavenis its own git root (init’d) so Godot files aren’t swallowed by the parent~/Claudeignore-everything gitignore.
7. Multiplayer-aware decisions (Phase 1 cost: ~0)
Section titled “7. Multiplayer-aware decisions (Phase 1 cost: ~0)”Disciplined choices now so Phase 3 co-op is additive:
- Fixed deterministic sim tick, decoupled from render (constant dt).
- Centralized seeded RNG.
- Input as data (
InputStatestruct) — remote player = another input source. - No engine physics for the swarm (own spatial hash) → deterministic + serializable.
- Sim state is plain serializable data → snapshot/diff for state-sync netcode.
No networking code is written in Phase 1.
7.1 Phase 3 netcode reference (decided, for later)
Section titled “7.1 Phase 3 netcode reference (decided, for later)”Reuse the authoritative-server + client-prediction model proven in Chris’s
chess-moba project (server/src/match-room.ts + src/net/prediction.ts): one
authoritative Sim per match owns truth, clients send tick-stamped input commands, the
server ticks and broadcasts snapshots, clients predict locally for responsiveness. Do
not use the chessdefense-versus-relay lockstep relay — it assumes 2 fixed slots and
does not scale to 2–32 players.
Transport nuance: chess-moba runs its Sim in TypeScript inside a Cloudflare Durable
Object; our sim is GDScript and cannot run in a DO. So the Phase-3 authority will be
Godot ENet host-authority or a headless Godot dedicated server, with chess-moba’s DO
model as the design discipline reference, not the literal stack.
7.5 Prior art & reuse (from Chris’s existing projects)
Section titled “7.5 Prior art & reuse (from Chris’s existing projects)”moba-bakeoff/slice-godot/core/sim.gd— sameextends RefCounted/class_name Sim, no-scene-tree, fixed-tick, seeded-RNG pattern we use here. Our conventions match it deliberately. Mirror itssnapshot_string()+ per-tick-trace determinism check (tests/determinism_check.gd) — it pinpoints the exact tick of any divergence and doubles as Phase-3 desync detection.chess-mobaspawn cadence (maybeSpawnWave()) validates the time-escalating spawn director approach.
8. Explicitly out of scope for Phase 1
Section titled “8. Explicitly out of scope for Phase 1”- Multiplayer / networking (Phase 3).
- Meta-progression / persistent unlocks / currency (Phase 2).
- Multiple characters, multiple maps (Phase 2).
- Endless mode UI/flow (designed-for, not built).
- Final art/audio polish beyond the neon code-drawn baseline.
- Store/export packaging for each platform (separate go-live effort).
9. Success criteria for Phase 1
Section titled “9. Success criteria for Phase 1”- A run is genuinely fun and replayable: different upgrade picks produce different play.
- 60 FPS desktop at several-thousand-entity densities; smooth on a test mobile device.
- The build-craft pillar is real: at least 2 distinct viable build paths via the 5 weapons, transformative mods, and evolutions.
/simcovered by passing headless GUT tests, including an RNG-determinism test.- Clean sim/render/input separation verified (no gameplay logic in the render layer).