Field Notes · kept as written, during the work

Star Swarm — The Build Log

The milestone status reports kept during the build — formation, entrances, the review team, dives, the beam, and the rescue, as they landed.

Back to the guide: /tutorials/star-swarm · These are the status reports written during the build, kept as filed — milestone by milestone.

_Updated 2026-08-29_

Milestone 1 — Formation: complete

  • Player moves with ←/→ (or A/D), clamped to the screen, plus a short ↑/↓ (W/S) band — 80px forward, 12px back — for dodging; Space fires (max 2 bullets alive, edge-triggered).
  • 40 enemies in 5 ranks (4 boss / 8 lancer / 8 lancer / 10 drone / 10 drone) pre-placed in a breathing, drifting formation.
  • Bosses have 2 HP and turn pink at 1 HP. Scores: boss 150, lancer 80, drone 50.
  • 3 lives (loseLife exists; nothing can kill the player yet).
  • Stage-clear overlay when the formation is empty; Enter advances to the next stage (same formation, score/lives kept).
  • Entrance dives dip to y≈528, overlapping the ship's forward limit (top edge y=501) but clear of its rest height — pushing forward during a wave is a risk.
  • Enemy→ship contact destroys the enemy (scored), costs a life; the lane stays empty for 1.6 s (explosion plays out), then the replacement ship arrives with a 'ready' cue and blinks through 1.5 s of grace.
  • Shot/hit tracking per stage and per game. Stage clear awards an accuracy bonus by hit ratio (100% → 5000, ≥90% → 3000, ≥75% → 2000, ≥60% → 1000, ≥40% → 500) and shows shots / hits / ratio / bonus; game over shows the overall hit ratio.
  • Scrolling starfield with horizontal parallax: stars counter-scroll against the ship's sideways motion, near stars more than far.
  • 27 tests: formation math, collisions, boss HP, reducer behaviour, purity check, and a fixed-clock integration sim that clears the stage.

Milestone 2 — Entrances: complete (branch feat/m2-entrances)

  • src/logic/paths.js: Catmull-Rom splines, arc-length reparameterised (constant speed), mirrorPath, registry sweepLeft / sweepRight (mirror pair) / plunge.
  • src/logic/waves.js: wave format validation (all errors collected), launch schedule, arrival blend retargeting the last 15% to the slot's projected-arrival position.
  • Enemy state machine pending → entering → settling → inFormation; pending enemies invisible and untargetable.
  • Stage 1 = 4 waves over all 40 slots, entrance ≈12 s; stage 2+ reuse the table at speedScale 1.1.
  • docs/specs/wave-format.md documents the format.

Audio (branch feat/audio)

  • src/engine/audio.js: all sounds synthesized with Web Audio (oscillators + LCG noise) — nothing sampled or copied.
  • Events → sounds: fired laser zap, enemyLaunched dive whistle, enemyHit thunk, enemyKilled pop+burst, stageClear sting, lifeLost fall.
  • Formation hum: a tremolo-pulsed drone runs while enemies sit in formation; pitch, pulse rate and brightness rise as the formation thins.
  • Unlocks on first key press (browser gesture rule); M toggles mute.

Milestone 3 — Review team: complete

  • feel / bugs / threat reviews → tuning changelist and docs/specs/attack.md. Prerequisite fixes landed with M4 (stage-clear vs game-over precedence, pathPoint NaN guard, arrival blend blendStart 0.7).

Milestone 4 — The attack: complete (merged 2026-08-29, 249 tests)

  • src/logic/rng.js seeded xorshift; dives.js cadence/unlocks/diver selection/templates parameterised by player x and stage (diveBottomY ramps 490 → 530), return paths with the 100/170 px blend guardrails, aimed fire (drone straight, lancer closed-form lead, kamikaze once), escorts; capture.js sweeping beamCone, capture accumulation, boss/player/captive/dual transition tables + transition().
  • Entities: enemy dive/return/escort/beam states, enemy bullets, captive, dual fighter (2 hitboxes, 2 origins, 2 bullets per ship, drop-to-single, persists across stages).
  • Visuals/audio: animated beam cone drawn from the logic triangle, enemy bullets, captive above the boss, dual render, boss beaming tint, SHIP CAPTURED / FIGHTER RESCUED banners, HUD dual indicator, beam loop + 7 new synthesized cues, starfield dims while beaming.
  • Tests: table-driven capture transitions (both boss-death branches, player-death-during-rescue, dual beam), seeded cadence bounds, fixed-clock dive paths, aim, escorts, fire cap, one-boss-off-formation, breakaway-vs-cap, stage-clear precedence, 60 s seeded attack sims.

Milestone 5 — Challenge pack: complete (merged 2026-08-30, 317 tests)

  • Wave format: challenge: true stages with count groups, off-screen-exit rule, global timing-overlap rule (MIN_PATH_GAP_PX 40) — all backward compatible; documented in docs/specs/wave-format.md.
  • Five showpiece paths: figureEight (full-screen, self-crossing), sidewinder + mirror, loopDeLoop (true 540° loop), corkscrew.
  • Three challenge stages (stages 3, 6, 9, cycling): 40 enemies each, enter → showpiece → exit, no dives/fire; 36 s / 27 s / 36 s; zero enemy-enemy overlaps (min separation 32 / 29 / 27 px). Perfect clear (40 bullet kills) → +10,000; hit ratio still shown, no accuracy bonus.
  • npm run validate-waves CLI (exit 1 on failure; accepts a module path).
  • UI/audio: CHALLENGE STAGE banner, HUD kills/40, CHALLENGE CLEAR screen with PERFECT / NO BONUS, exit and perfect cues.
  • qa's scripted pilot reaches 23 / 31 / 24 of 40 — perfecting a challenge stage takes real play.

Normal-stage variety (2026-08-30)

  • Four more entrance paths: hookLeft/hookRight (low side entry, hook up), cascadeLeft/cascadeRight.
  • Stage 2 (bosses lead, cascades + hooks) and Stage 3 (drones rush low first, bosses last down the cascades) tables; normal stages cycle 1 → 2 → 3 between challenge stages. All validate; 318 tests.

iPhone / iPad (2026-08-30) — the Brick Break checklist applied

  • Page lockdown CSS (touch-action none, overscroll none, fixed body, no callout/select), viewport-fit=cover + safe-area padding, 100dvh, portrait 3:4 canvas fitted by width AND height.
  • DPR-scaled backing store (cap 3) in src/engine/viewport.js, re-fit on resize / orientation / visualViewport.
  • Touch input (src/engine/input.js, Pointer Events): drag anywhere = relative ship movement via the pure nudgePlayer; quick tap = one shot (or start on menus); hold the bottom-right fire pad or a second finger = autofire; top-right tap = mute. Non-passive touchmove preventDefault for older Safari. Audio unlocks on first touch as well as keydown.
  • Pure-CSS "turn your phone upright" prompt for landscape phones. On-canvas touch hints appear once a touch is seen.
  • vercel.json + .vercelignore for phone-test deploys; on LAN use ipconfig getifaddr en0 + the port.
  • No ctx.roundRect in the renderers (nothing to feature-detect).

Extra ships (2026-08-30)

  • A bonus ship every 10,000 points (EXTRA_LIFE_EVERY), awarded on the same step the score crosses the line (kills, accuracy and perfect bonuses all count); carried across stages. extraLife event → EXTRA SHIP banner + sting. A hit that both earns and costs a ship on the same step nets zero instead of game over.

Capture frequency retune (2026-08-30)

  • Boss dives unlock below 75 % formation remaining (was 25 %), beam-intent roll 0.5 (was 0.2), line-of-sight window ±100 px (was 60). Constants: DIVE_UNLOCK, BEAM.intentChance, BEAM.lineClearXRange.

Next

  • Playtest tuning: challenge stage speeds (are perfects achievable?).

Rescue fix (2026-08-30)

Bug: after a capture the boss ascended home in ~1 s, beating the 1.6 s respawn, so the first shot the player could fire hit an in-formation captor → captiveDestroyed (silent — same kill cue as any other enemy). The re-dive rescue path worked in the reducer but the captor was never preferred for a launch, so in play "shoot the boss" never yielded the dual fighter. selectDivers now gives a captive-holding boss the next launch (attack.md §2 exception); main.js shows a CAPTIVE LOST banner on captiveDestroyed. Tests: captor-priority unit cases + an end-to-end rescue-loop sim in tests/integration/attack-sim.test.js.

Straight-down fire on early stages (2026-08-30)

Enemy shots on stages 1–3 now drop straight down (aimDown); aimed drone fire and lancer lead start at stage 4 (ENEMY_BULLET.aimedFromStage). Tommy's playability call — the early game was too punishing with every diver leading the ship.

← Back to the guide