Inspiration
Forty-odd years ago, my dad wrote a hidden-fleet space combat game in Turbo Pascal on hardware that now props up somebody's garage table. Two players, one floppy disk, a green CRT, and enough quiet tension to remember decades after the floppy rotted. This hackathon gave me a reason to exhume it. Same bones, new century, new nebula.
What it does
Battlestations: Nebula is a turn-based, hot-seat space combat game for two players sharing one browser. You each hide a fleet of five ships on an 80-by-20 grid, then take turns poking the dark with probes and firing lasers and missiles at coordinates you hope are still accurate.
- Five fixed ships per side: a Battleship, a Probe Ship, two Destroyers, and a Cruiser.
- One of four actions per ship per turn: Probe, Shoot Laser, Launch Missile, or Move.
- Energy allocation sliders per ship split your reactor between shield regen and laser power. Shields fill first when energy is tight.
- Probes illuminate a 4-by-4 box (6-by-6 from a Probe Ship) of the nebula and reveal any enemy ships inside. The lights go out after two of your turns. What stays is a ghost marker.
- Hidden-information integrity lives or dies on the handoff screen, which reports hit counts only. No ship names, no positions, no damage numbers.
How I built it
One hundred percent spec-driven, start to finish. The workflow ran scope → PRD → spec → checklist → build → iterate, all captured in committed docs:
docs/scope.mdsets what's in, what's out, and why.docs/prd.mdlocks product behavior, screen flow, and combat math.docs/spec.mddefines technical architecture, data models, and file layout.docs/checklist.mdis twelve atomic build items, each referencing the spec section it implements, with explicit acceptance criteria. Two iterations layered on top for audio and submission polish.process-notes.mdcaptured decisions, pushback, and trade-offs live through every phase.
Stack is Godot 4.6.2 with GDScript, statically typed throughout. HTML5 export runs in-browser via itch.io with no download. Each grid uses a SubViewport + Camera2D for native zoom and pan, and fog-of-war is a sparse Dictionary keyed on Vector2i to keep lookup O(1) across the 1,600-cell board.
Challenges I ran into
- Ghost-ship offset during fleet placement. The placement scene's ghost ship tracked cursor motion was wrong through four fix rounds. Root cause was hand-rolled container-to-viewport math that ignored Godot's internal SubViewportContainer transforms. The fix was replacing the math with
get_local_mouse_position()on the renderer node. The engine already knew the answer. I was asking the wrong question. - Probe fade state machine. Probe intel degrades over your subsequent turns and has to survive overlap, ship movement, and damage updates. Getting the sparse Dictionary to converge on the correct state took three fixes: probe records not updating when a ship moved into or out of a probe, shield/armor values going stale after a hit, and overlap not resetting prior ghost markers cleanly.
- Move cost counted rotation as displacement. Rotating a ship shifted its origin cell, and the cost calculator treated that shift as movement. Fixed by anchoring the cost to the pivot points instead of the origin.
- Audio that sounds like space, not like a fan project. First pass synthesized sounds with numpy. They sounded fake. Scrapped and pivoted to Kenney CC0 packs with ffmpeg fade-ins to smooth the onset. Better audio in an hour than numpy produced in four.
Accomplishments I'm proud of
- The probe fade mechanic is load-bearing. Stale intel costs you. The ghost marker is worse than no intel because it feels like information.
- The nebula is a surface you read, not a backdrop you ignore. Probes illuminate the painted cloud rather than replace it. Zoom out and the whole 80-by-20 battlefield reads like weather.
- Hidden-information integrity stayed honest end-to-end. You never see the opponent's grid. The handoff screen reports only hit counts.
- Every document in the project supports the next one. The PRD's combat math became the spec's
resolve_laser, which became the checklist's item 9, and became working code. No phase was busywork.
What I learned
- Front-loading specs is the payoff. I came in with GitHub epic/story experience and thought I already specified work deeply. This process made the gap obvious. The spec's data model section survived untouched through the whole build because it got argued over before a line of code was written.
- Voice locked early prevents rewrites later. The Welcome tutorial page voice got approved before any marketing copy or itch.io description was drafted. Everything downstream cribbed from that anchor and stayed consistent.
- The checklist is a living document, not a plan. When items broke or surfaced gaps, the right move was to revise the checklist, not push through. Mid-iteration additions (the color scheme doc, the Cowork handoff brief) slotted in cleanly because the structure was built to adapt.
What's next for Battlestations: Nebula
docs/backlog.md holds the post-hackathon pile. Ranked roughly by impact: a randomize-fleet button for quick games, miss indicators on the Target Grid so you can see where you've already fired, instant-win on the killing shot, and a historical-probe overlay showing cells you've previously scanned. Bigger structural ideas (non-linear ship shapes, critical hit zones, partial probe reveal) each want their own design pass before any code lands.
A note on the process
The plugin workflow was the best part of this hackathon for me. I'm going to keep using it to iterate on Battlestations: Nebula after submission. The backlog is long, and the process makes planning each subsequent pass easier than the last. Thanks for running this.
Log in or sign up for Devpost to join the conversation.