🔥 The S.P.A.R.K. That Lit This Fuse
🤖 Prompt given to GitHub Copilot (Claude Sonnet 4.6) — 2026-05-01
“Claude, you are a professional ‘coding mentor’ for a high school student, and you want them to be marketable in the tech/coding world upon high school graduation. Assuming they studied a previous app, origRocket.html and its affiliated files and internalized the laundry list suggestions from that app’s laundry list, what are the top 3 suggestions you’d make for elevating the origBomb app (without worrying about styles or SWClass incorporation (yet)). For reference, please summarize the main checklist ideas from the attached laundry list (it would be cool if you could provide a rocket or bomb-themed acronym!) and then provide your specific recommendations for this current app, origBomb.html and its associated files. Mimic the type of styles you used in the prior laundry list, and include this prompt so users can ‘see’ how we communicate!. Call this new file laundryListBombStage2.html Feel free to link back to Griffin’s rocket app if that helps.”
🧺 What Is a Laundry List?
A laundry list is a planning tool — a checklist of specific improvements to make before you start typing. It prevents “spaghetti sessions” where you try to fix five things at once and accidentally break everything.
The non-negotiable rule for Stage 2: when we’re done, the bomb must look exactly the same as Stage 1. Clouds in the same spots. Bomb body unchanged. Particles still explode on click. The only thing changing is how the code is written and organized. This is called refactoring.
If you haven’t already read Griffin’s Rocket Saga — Stage 2 Laundry List, do that first. That page walks through each improvement with full before/after examples. This page builds on those ideas, so you’ll want them internalized.
🚀 The ROCKETS Recap
The Rocket Saga’s Stage 2 laundry list covered 7 improvements. Use the acronym ROCKETS to remember them. (Yes, it’s fitting that the rocket gave us the framework we now apply everywhere else.)
| 🚀 | Improvement | Key Idea |
|---|---|---|
| R | Read me first | Add a professional file header (who wrote it, when, what it does) |
| O | Only named constants, no magic numbers | Replace every bare literal with a descriptive const |
| C | Color palette up front | Group all colors as named arrays at the top of the file |
| K | Keep draw() thin |
Decompose draw() into named helper functions (table of contents) |
| E | Early setup = stable data | Pre-generate arrays/objects in setup(), not in draw() |
| T | Tagged sections | Add // === section divider comments as visual landmarks |
| S | State independence | Each function explicitly sets its own fill/stroke state — no silent inheritance |
🔗 Full before/after examples for every letter above: Griffin’s Rocket Stage 2 Laundry List
💣 The Bomb Brings Something New
The bomb sketch has all the same problems the rocket had — no file header, magic
numbers everywhere, a monolithic draw(), and drawing state that bleeds
silently between shapes. All of those need the full ROCKETS treatment.
But the bomb also introduces two things the rocket sketch didn’t have:
-
A JavaScript class (
Particle) — already great object-oriented thinking! But the class itself is packed with hardcoded “magic numbers” (velocities, gravity, size ranges, fade rates) that have no names and no explanation. The named-constants discipline from ROCKETS needs to extend inside the class. - Dead code — variables and settings that were declared with clear intent but were never actually used. They leave false trails for any future reader (including future-you, six weeks from now).
The top 3 improvements for the bomb form their own acronym: TNT. Of course they do.
💥 The TNT Improvements
| 💣 | Improvement | Key Concept | Visual Change? |
|---|---|---|---|
| T | Tackle the full ROCKETS checklist | Header + constants + colors + decomposition + state ownership | None |
| N | Name the magic numbers inside Particle |
Named constants extend into classes too | None |
| T | Terminate dead code | Remove zombie variables & zombie configuration | None |
Every letter of ROCKETS applies to origBombSketch.js. The most visible
payoff comes from Keeping draw() thin: the original is a
single 50+ line block that mixes clouds, bomb geometry, clock-face details, and particle
updates all in one place. There is no visual structure at all.
After decomposition, draw() becomes a four-line table of contents.
Anyone can read the structure of the entire sketch in under five seconds.
function draw() {
background(135, 206, 235);
noStroke();
fill(255);
// Draw a cluster of circles to form one cloud
circle(50, 100, 50);
circle(80, 100, 70);
circle(110, 100, 50);
circle(80, 80, 50);
circle(330, 200, 50);
circle(360, 200, 70);
circle(390, 200, 50);
circle(360, 180, 50);
fill(10, 0, 0);
stroke(0);
rect(110, 100, 180, 180) // <-- also: missing semicolon!
triangle(200, 100, 330, 330, 70, 330);
circle(200, 100, 180.3);
stroke(100);
strokeWeight(4);
line(200, 260, 200, 330);
line(105, 260, 65, 333);
line(295, 260, 336, 333);
fill(255, 255, 0);
noStroke();
circle(200, 100, 100);
// ... clock face triangles, inner circles, stroke ring ...
// ... 20 more lines ...
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].show();
if (particles[i].finished()) {
particles.splice(i, 1);
}
}
}
AFTER — draw() is a 4-line table of contents
// ── Main draw loop ────────────────────────────────────────────────────────
// draw() is called by p5 at FRAME_RATE times per second.
// Its only job is to call helpers in the right order (back to front,
// like layers in Photoshop). All the real work is in the helpers below.
function draw() {
drawBackground(); // paint the sky (erases last frame)
drawClouds(); // two fluffy clouds
drawBomb(); // full bomb: body, clock face, fuse lines
updateParticles(); // advance and render the explosion particles
}
AFTER — sample helper function with constants and explicit state
// ── Canvas / Frame ──────────────────────────────────────────────────────────
const CANVAS_W = 400;
const CANVAS_H = 400;
const FRAME_RATE = 60;
// ── Bomb geometry ────────────────────────────────────────────────────────────
// The outer circle defines the bomb. Every other measurement is derived
// from these three values, so repositioning the bomb means changing
// BOMB_CX and BOMB_CY — nothing else.
const BOMB_CX = 200; // horizontal center of the bomb
const BOMB_CY = 100; // vertical center of the bomb (top of canvas area)
const BOMB_D = 180; // outer bomb diameter (original used 180.3)
const BOMB_R = BOMB_D / 2; // radius = 90 — derived, not a new magic number
// The background rect that gives the bomb a "solid" look
const BOX_X = BOMB_CX - BOMB_R; // 110 (left edge)
const BOX_Y = BOMB_CY; // 100 (top edge)
const BOX_W = BOMB_D; // 180 (same as bomb diameter — square box)
const BOX_H = BOMB_D; // 180
// ── Clock face ───────────────────────────────────────────────────────────────
const CLOCK_D = 100; // outer yellow circle diameter
const CLOCK_RING_D = 90; // inner yellow stroke ring
const CLOCK_DOT_D = 20; // small yellow center dot
const CLOCK_NUB_D = 15; // tiny black center nub
const CLOCK_RING_W = 10; // stroke weight of the ring
// ── Fuse lines ───────────────────────────────────────────────────────────────
const FUSE_TOP_Y = 260; // where all three fuse lines start (y)
const FUSE_C_X = 200; // center fuse — x stays at BOMB_CX
const FUSE_BOT_Y = 330; // straight fuse bottom tip
const FUSE_L_TOP_X = 105; // left fuse top x
const FUSE_L_END_X = 65; // left fuse bottom x
const FUSE_L_END_Y = 333; // left fuse bottom y
const FUSE_R_TOP_X = 295; // right fuse top x
const FUSE_R_END_X = 336; // right fuse bottom x
const FUSE_R_END_Y = 333; // right fuse bottom y
const FUSE_WEIGHT = 4; // fuse stroke weight
// ── Color palette ────────────────────────────────────────────────────────────
// Each entry is [r, g, b]. Use with fill(...CLR_NAME) or fill(...CLR_NAME, a)
const CLR_SKY = [135, 206, 235]; // sky blue background
const CLR_CLOUD = [255, 255, 255]; // white clouds
const CLR_BOMB = [ 10, 0, 0]; // near-black bomb body (tiny red tint)
const CLR_OUTLINE = [ 0, 0, 0]; // black outlines
const CLR_FUSE = [100, 100, 100]; // medium gray fuse lines
const CLR_CLOCK_FACE = [255, 255, 0]; // bright yellow clock face
const CLR_CLOCK_HAND = [ 0, 0, 0]; // black clock hands
// ── drawBackground ────────────────────────────────────────────────────────────
function drawBackground() {
background(...CLR_SKY); // p5 spread operator trick from the Rocket Saga
}
// ── drawClouds ────────────────────────────────────────────────────────────────
// Each cloud is a cluster of overlapping circles.
// noStroke() is declared here explicitly — never rely on inherited state.
function drawClouds() {
noStroke();
fill(...CLR_CLOUD);
// Cloud 1 — upper left
circle( 50, 100, 50);
circle( 80, 100, 70);
circle(110, 100, 50);
circle( 80, 80, 50);
// Cloud 2 — right side
circle(330, 200, 50);
circle(360, 200, 70);
circle(390, 200, 50);
circle(360, 180, 50);
}
// ── drawBomb ──────────────────────────────────────────────────────────────────
// Orchestrates all bomb sub-parts in back-to-front drawing order.
function drawBomb() {
drawBombBody();
drawFuseLines();
drawClockFace();
}
draw() after the refactor: four.
Compare that to the original 50+. Anyone reading the refactored version
— even without knowing p5.js — understands the sketch in seconds:
sky, clouds, bomb, particles. When a bug shows up in the clock face, you open
drawClockFace() — you don’t hunt through 50 entangled lines.
Also notice that the missing semicolon after rect(110, 100, 180, 180)
in the original would have been immediately obvious inside a short,
focused function — another free benefit of decomposition.
Particle
The Particle class is the most sophisticated code in the sketch —
and it came with a comment that says “I had some AI help with this part.”
That’s completely fine! Learning when and how to use AI assistance is itself a
professional skill. But the next step is to understand and own the code you
accepted.
Right now the class is full of bare literals: random(5, 20),
0.15, 0.95, random(4, 120),
4, and 150 in mousePressed(). What does
0.95 mean? What does 4 do? A reader has to trace the
physics mentally before understanding anything.
Named constants make the physics readable — and make it trivially easy to tune the explosion later without hunting through the class internals.
BEFORE — Particle class with unexplained magic numbersclass Particle {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D().mult(random(5, 20)); // ← what range?
this.acc = createVector(0, 0.15); // ← what is 0.15?
this.alpha = 255;
this.size = random(4, 120); // ← why 4 to 120?
this.color = color(random(200, 255), random(0, 100), 0);
}
update() {
this.vel.mult(0.95); // ← 0.95 is "air drag" — but you'd never know
this.vel.add(this.acc);
this.pos.add(this.vel);
this.alpha -= 4; // ← fade speed — but why 4?
}
// ...
}
function mousePressed() {
for (let i = 0; i < 150; i++) { // ← why 150? is that adjustable?
particles.push(new Particle(mouseX, mouseY));
}
}
AFTER — named constants expose the physics vocabulary
// ── Particle configuration ────────────────────────────────────────────────────
// Keeping all tuning knobs together means tweaking the explosion is a
// one-section edit. Change a number here, and the behavior updates everywhere.
const PARTICLE_COUNT = 150; // sparks spawned per mouse click
const PARTICLE_SPEED_MIN = 5; // slowest spark launch speed (px/frame)
const PARTICLE_SPEED_MAX = 20; // fastest spark launch speed (px/frame)
const PARTICLE_GRAVITY = 0.15; // downward acceleration per frame
const PARTICLE_DRAG = 0.95; // speed multiplier per frame (air resistance)
const PARTICLE_ALPHA_DEC = 4; // how fast each spark fades (per frame, out of 255)
const PARTICLE_SIZE_MIN = 4; // smallest spark diameter (px)
const PARTICLE_SIZE_MAX = 120; // largest spark diameter (px)
const PARTICLE_RED_MIN = 200; // minimum red channel — keeps sparks in the red-orange range
const PARTICLE_RED_MAX = 255; // maximum red channel
const PARTICLE_GRN_MAX = 100; // max green — raising this pushes sparks toward yellow
class Particle {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D().mult(random(PARTICLE_SPEED_MIN, PARTICLE_SPEED_MAX));
this.acc = createVector(0, PARTICLE_GRAVITY);
this.alpha = 255;
this.size = random(PARTICLE_SIZE_MIN, PARTICLE_SIZE_MAX);
this.color = color(random(PARTICLE_RED_MIN, PARTICLE_RED_MAX), random(0, PARTICLE_GRN_MAX), 0);
}
update() {
this.vel.mult(PARTICLE_DRAG); // drag slows the spark each frame
this.vel.add(this.acc); // gravity accelerates it downward
this.pos.add(this.vel); // advance position
this.alpha -= PARTICLE_ALPHA_DEC;
}
// ...
}
function mousePressed() {
for (let i = 0; i < PARTICLE_COUNT; i++) {
particles.push(new Particle(mouseX, mouseY));
}
}
PARTICLE_GRN_MAX:
“raising this pushes sparks toward yellow.”
Before the refactor, discovering that relationship required mentally simulating
the RGB values. After, it’s a single sentence. That’s the real
power of named constants — they document the intent and the
effect, not just the value.
This principle works just as well inside a class as it does at the top of a file.
Constants belong wherever the magic numbers are — not just at the global level.
Dead code is code that exists in a file but has no effect on the
running program. It ranges from harmless to actively misleading. In
origBombSketch.js there are three examples, each telling a slightly
different story about how dead code gets created.
Dead code #1 — a zombie variable
let angle = 0; is declared at the very top of the file but never read
or updated anywhere. It’s a zombie variable: it was born with a
purpose (perhaps the student planned to animate the clock hands) but that plan was
abandoned. The variable still takes up a name in the file and silently confuses
every reader who asks: “where is angle used?”
Dead code #2 — zombie configuration
angleMode(DEGREES); in setup() sets p5 to use degrees
instead of radians. The comment even explains why:
“Using degrees is easier for 120-degree spacing.” But
no angle calculations exist anywhere in the sketch. The clock hands are
all drawn as hardcoded triangle() coordinates, not with rotation.
The comment describes an intention that was never implemented. This is
aspirational code — the plan lived, but the code to execute it
never arrived.
Dead code #3 — a redundant call
In draw(), immediately before drawing the clock ring:
fill(0); is called, then on the very next line it is overridden by
noFill();. The fill(0) has zero effect —
it sets a color that is immediately discarded. This is the subtlest kind of dead
code: a leftover from an edit that was partially completed.
let angle = 0; // declared here, never used anywhere — zombie variable
let particles = [];
function setup() {
createCanvas(400, 400);
angleMode(DEGREES); // Using degrees is easier for 120-degree spacing
// ↑ the intent was real; the implementation never arrived — zombie config
}
// ... inside draw(), drawing the clock ring ...
fill(0); // ← sets black fill
noFill(); // ← immediately cancels the fill — fill(0) is dead
stroke('yellow');
strokeWeight(10);
circle(200, 100, 90);
AFTER — clean, every line earns its place
// let angle = 0; ← removed: never used.
// If rotation is added in a future stage, re-introduce it then.
let particles = [];
function setup() {
createCanvas(CANVAS_W, CANVAS_H);
frameRate(FRAME_RATE);
// angleMode(DEGREES) removed: no angles are calculated in this sketch.
// If you add rotation later (e.g., spinning clock hands), add it back then.
}
// ... inside drawClockFace(), drawing the ring ...
noFill(); // explicitly no fill — the ring is stroked only
stroke(...CLR_CLOCK_FACE); // yellow ring
strokeWeight(CLOCK_RING_W);
circle(BOMB_CX, BOMB_CY, CLOCK_RING_D);
One more bonus: removing
angleMode(DEGREES) also removes a subtle
trap for Stage 3 and beyond. When SketchWaveJS classes start using p5’s
trigonometric functions, they will expect the default radian mode. A
“zombie” degrees setting left in the file could cause
hard-to-trace rotation bugs much later.
✅ Stage 2 Checklist
| 💣 | Task | Key Concept | Visual Change? |
|---|---|---|---|
🚀 Apply the ROCKETS checklist to origBombSketch.js
|
|||
| R | Add a file header block | Documentation & professionalism | None |
| O | Replace all magic numbers with named constants | Readability & maintainability | None |
| C | Give all colors meaningful names | Intent clarity & easy retheme later | None |
| K | Break draw() into helper functions |
Decomposition & separation of concerns | None |
| E | Move any one-time setup into setup() |
Performance & the setup/draw contract | None |
| T | Add section divider comments | Navigation in larger files | None |
| S | Each function explicitly sets its drawing state | Encapsulation & bug prevention | None |
| 💣 Bomb-specific additions (TNT) | |||
| N | Extract named constants from Particle class |
Constants belong inside classes too | None |
| T | Remove let angle = 0 (never used) |
Dead code elimination | None |
| T | Remove angleMode(DEGREES) (intention without implementation) |
Dead code / zombie config | None |
| T | Remove redundant fill(0) before noFill() |
Dead code / leftover from partial edit | None |
🔭 What Comes After Stage 2?
Once the code is clean and well-organized, we’ll be ready to introduce SketchWaveJS classes and apply site-wide styles. That’s Stage 3 and beyond.
Think about what Stage 3 will look like after this refactor:
replacing drawBombBody() with an SW class call is a one-function
edit. Compare that to hunting through 50 tangled lines looking for the right
circle() call without breaking the clock face coordinates.
The Particle class is also Stage 3 – ready the moment it has
named constants. Down the road, it could become an SWParticle
class that inherits from a SketchWave base class — a clean migration
because the class already has clear, single-purpose methods and named configuration.
That’s the payoff of refactoring: every future change gets easier. The effort invested in Stage 2 is never wasted — it’s the foundation that everything else is built on.