⛽ The AI Fuel That Launched This Page
🤖 Prompt given to GitHub Copilot (Claude Sonnet 4.6) — 2026-04-30
“Claude, you are a specialist with p5js and with SketchWaveJS. You are also a devoted high school teacher who wants to teach novices (ordinary high school students) how to be excited and competitive programmers who are ‘work-ready’ when they leave high school. Please study the attached files and create a file, ‘laundryListRocketStage2.html’ (that will fit into the SketchWave ecosystem as shown in the rocket index page), that outlines the improvements you’d make in the origRocketSketch.js file (no style or SW updates yet!). We’d like the image to remain the same, but we want big improvements in the organization of the JS before we bring in SW classes and/or styles. Please add lots of comments as to your reasoning for the improvements. Also, because we are learning how to leverage AI, please include this prompt as an example of the ‘fuel’ that got you going!”
🧺 What Is a Laundry List?
In programming, a laundry list is a planning tool — a checklist of specific improvements to make before you start typing. Professional developers use them constantly. They prevent “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 rocket must look exactly the same as Stage 1. Stars still twinkle. Colors are identical. Every shape sits in the same spot. The only thing changing is how the code is written and organized. This is called refactoring — and it is one of the most valuable skills a developer can have.
Refactoring without breaking anything requires discipline. A laundry list keeps you focused on one change at a time.
🔧 The Improvements
Every file you write should open with a comment block that answers four questions:
Who wrote it? When? What is it? What does it do?
This is industry standard. Without it, the next person to open the file — including
future-you — has to guess. The original origRocketSketch.js has none.
function setup() {
createCanvas(600, 400);
frameRate(60);
}
AFTER — a professional file header
/*
* Coder: Griffin + klp
* Date: 2026-04-30
* File: rocketSketch.js
* Purpose: A p5.js rocket on a starfield. Stage 2 refactor:
* same visual output as Stage 1, code reorganized for
* clarity, constants, and helper functions.
* No SketchWaveJS classes yet — that's Stage 3.
*/
function setup() {
createCanvas(CANVAS_W, CANVAS_H);
frameRate(FRAME_RATE);
}
A magic number is a bare literal value buried in code with no
explanation. rect(260, 150, 80, 150) — what is
260? What is the second 150? Without reading the surrounding
lines carefully you cannot know. Named constants fix this instantly and unlock a huge
bonus: if you ever want to move the rocket, you change one number
and everything repositions automatically.
Notice below how BODY_CX, BODY_R, and BODY_BOT
are derived from the body constants. No new magic numbers are needed anywhere
in the rest of the file.
// What does any of this mean to a reader picking it up for the first time? rect(260, 150, 80, 150); triangle(260, 150, 340, 150, 300, 100); ellipse(300, 190, 40, 40); triangle(260, 280, 230, 330, 260, 330); triangle(340, 280, 370, 330, 340, 330); triangle(280, 300, 320, 300, 300, 350);AFTER — named constants that document themselves
// ── Canvas ────────────────────────────────────────────────────────────── const CANVAS_W = 600; // canvas width (px) const CANVAS_H = 400; // canvas height (px) const FRAME_RATE = 60; // target frames per second // ── Rocket body geometry ───────────────────────────────────────────────── // The body is a rectangle. ALL other parts are mathematically derived // from these four values, so moving the rocket means changing just // BODY_X and/or BODY_Y — nothing else. const BODY_X = 260; // left edge of the rocket body const BODY_Y = 150; // top edge of the rocket body const BODY_W = 80; // width of the rocket body const BODY_H = 150; // height of the rocket body // Derived edges — computed once, used everywhere below. // Naming these avoids repeating the arithmetic (and avoids mistakes). const BODY_CX = BODY_X + BODY_W / 2; // horizontal center = 300 const BODY_R = BODY_X + BODY_W; // right edge = 340 const BODY_BOT = BODY_Y + BODY_H; // bottom edge = 300 // ── Nose cone ──────────────────────────────────────────────────────────── // The cone's base matches the body's top edge exactly (seamless join). const NOSE_TIP_Y = BODY_Y - 50; // apex of the nose cone = 100 // ── Porthole ───────────────────────────────────────────────────────────── const PORT_CY = BODY_Y + 40; // porthole center y = 190 const PORT_D = 40; // porthole diameter (px) // ── Fins ───────────────────────────────────────────────────────────────── const FIN_TOP_Y = BODY_Y + 130; // where fins attach to body = 280 const FIN_BOT_Y = BODY_BOT + 30; // bottom tip of fins = 330 const FIN_SPREAD = 30; // how far fins jut out sideways // ── Flame ───────────────────────────────────────────────────────────────── const FLAME_INSET = 20; // flame base is 20px inside body edges const FLAME_DROP = 50; // flame tip hangs 50px below body base // ── Stars ───────────────────────────────────────────────────────────────── const STAR_COUNT = 100; // number of stars drawn each frame const STAR_SIZE_MIN = 1; // smallest star diameter (px) const STAR_SIZE_MAX = 3; // largest star diameter (px) const STAR_ALPHA_MIN = 150; // dimmest star opacity (0–255) const STAR_ALPHA_MAX = 255; // brightest star opacity (0–255)
= signs and comments vertically might look like
a minor cosmetic choice, but it has a real effect: when values are in a column,
the differences between them jump out at a glance.
Consistent alignment is a mark of a developer who thinks about their readers.
fill(255, 100, 100) appears twice in the original.
What is it — red? Salmon? Coral? And do both calls use the same shade?
You’d have to count the digits to know. Named color arrays make the palette
explicit, self-documenting, and easy to change later.
We store each color as a JavaScript array so we can use the spread operator
(...CLR_BODY) to pass it directly to p5’s
fill(r, g, b) — a clean modern JS trick.
fill(180); // the body — gray? which gray? fill(255, 100, 100); // the nose — red-ish fill(100, 100, 255); // porthole — blue-ish fill(255, 100, 100); // fins — same red as nose? let me count... fill(255, 150, 0); // flame — orangeAFTER — a named color palette at the top of the file
// ── Color palette ──────────────────────────────────────────────────────── // Each color is [r, g, b]. Pass to p5 with: fill(...CLR_NAME) // or with alpha: fill(...CLR_NAME, alpha) // Keeping them together makes it trivial to retheme the whole sketch. const CLR_SKY = [ 20, 20, 70]; // deep navy night sky const CLR_BODY = [180, 180, 180]; // medium gray rocket body const CLR_STROKE = [ 50, 50, 50]; // dark gray outline const CLR_ACCENT = [255, 100, 100]; // rocket red — nose cone AND fins share this const CLR_PORTHOLE = [100, 100, 255]; // porthole blue const CLR_FLAME = [255, 150, 0]; // engine flame orange const CLR_STAR = [255, 255, 255]; // star white (alpha varies per star)
CLR_ACCENT.
Design intent is now visible directly in the code.
When the student decides to change the accent color in Stage 3,
they change one line.
draw() into Named Helper Functions
The original draw() is one 52-line block: stars, body, nose,
porthole, fins, flame — all tangled together. In professional code, each logical
piece gets its own function. The result is that draw() becomes a
table of contents, not a novel. A reader understands the whole
structure in three seconds without reading a single detail.
This is called decomposition — breaking a big problem into smaller, named, single-purpose pieces. It is the most important organizational skill in all of software development.
BEFORE — one 52-line draw() block, structure hiddenfunction draw() {
background(20, 20, 70);
// Draw stars
for (let i = 0; i < 100; i++) {
let x = random(width);
let y = random(height);
let starSize = random(1, 3);
noStroke();
fill(255, 255, 255, random(150, 255));
ellipse(x, y, starSize, starSize);
}
fill(180);
stroke(50);
strokeWeight(2);
rect(260, 150, 80, 150);
// ... 30+ more lines of shapes with no visual breaks ...
}
AFTER — draw() becomes a 3-line table of contents
// ── Main draw loop ────────────────────────────────────────────────────────
// draw() is called by p5 at FRAME_RATE times per second.
// Its only job here 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(); // fill the canvas with the night sky (erases last frame)
drawStars(); // 100 random twinkling stars
drawRocket(); // the full rocket (body → nose → porthole → fins → flame)
}
// ── drawBackground ────────────────────────────────────────────────────────
// Repaints the canvas every frame with the night-sky color.
// Without this, every frame's drawings would stack on top of each other.
function drawBackground() {
background(...CLR_SKY);
}
// ── drawStars ─────────────────────────────────────────────────────────────
// Draws STAR_COUNT stars at random positions every frame.
// Because x and y are re-randomized each frame, stars appear to twinkle
// AND drift slightly — that "lo-fi animation" is intentional for Stage 2.
// See Improvement #5 for a way to lock positions while keeping the twinkle.
function drawStars() {
noStroke();
for (let i = 0; i < STAR_COUNT; i++) {
let x = random(width);
let y = random(height);
let size = random(STAR_SIZE_MIN, STAR_SIZE_MAX);
fill(...CLR_STAR, random(STAR_ALPHA_MIN, STAR_ALPHA_MAX));
ellipse(x, y, size, size);
}
}
// ── drawRocket ────────────────────────────────────────────────────────────
// Orchestrates all rocket sub-parts in back-to-front drawing order.
// The body is drawn first (it's the "base"), then parts on top of it.
// The flame is drawn last so it appears to come out of the nozzle opening.
function drawRocket() {
drawRocketBody();
drawNoseCone();
drawPorthole();
drawFins();
drawFlame();
}
// ── drawRocketBody ────────────────────────────────────────────────────────
// The main cylindrical body — a rectangle with an outline.
function drawRocketBody() {
fill(...CLR_BODY);
stroke(...CLR_STROKE);
strokeWeight(2);
rect(BODY_X, BODY_Y, BODY_W, BODY_H);
}
// ── drawNoseCone ──────────────────────────────────────────────────────────
// A triangle capping the top of the body.
// Its base exactly matches the body top edge for a seamless join —
// no gap, no overlap. That's only obvious because we used named constants.
function drawNoseCone() {
fill(...CLR_ACCENT);
stroke(...CLR_STROKE);
strokeWeight(2);
triangle(
BODY_X, BODY_Y, // bottom-left = top-left of body
BODY_R, BODY_Y, // bottom-right = top-right of body
BODY_CX, NOSE_TIP_Y // apex of the cone
);
}
// ── drawPorthole ──────────────────────────────────────────────────────────
// A circular window, horizontally centered on the body.
function drawPorthole() {
fill(...CLR_PORTHOLE);
stroke(...CLR_STROKE);
strokeWeight(2);
ellipse(BODY_CX, PORT_CY, PORT_D, PORT_D);
}
// ── drawFins ──────────────────────────────────────────────────────────────
// Two symmetric fins at the base of the body, drawn before the flame
// so the flame sits visually on top of them.
function drawFins() {
fill(...CLR_ACCENT);
stroke(...CLR_STROKE);
strokeWeight(2);
// Left fin
triangle(
BODY_X, FIN_TOP_Y, // attaches to left edge of body
BODY_X - FIN_SPREAD, FIN_BOT_Y, // outer bottom corner
BODY_X, FIN_BOT_Y // inner bottom corner
);
// Right fin (exact mirror of left fin)
triangle(
BODY_R, FIN_TOP_Y,
BODY_R + FIN_SPREAD, FIN_BOT_Y,
BODY_R, FIN_BOT_Y
);
}
// ── drawFlame ─────────────────────────────────────────────────────────────
// A downward-pointing triangle exhaust flame.
// noStroke() is called explicitly here — never assume the previous
// function's stroke state carried over (see Improvement #7 for why).
function drawFlame() {
noStroke();
fill(...CLR_FLAME);
triangle(
BODY_X + FLAME_INSET, BODY_BOT, // flame base left
BODY_R - FLAME_INSET, BODY_BOT, // flame base right
BODY_CX, BODY_BOT + FLAME_DROP // flame tip
);
}
draw() now: three.
That’s it. Anyone reading this code — even without knowing p5.js —
can understand the structure in seconds.
When a bug shows up in the nose cone, you open
drawNoseCone() — you don’t hunt through 52 mixed lines.
Functions are the most powerful organizational tool in all of programming.
Learn to love them early.
setup()
slightly changes visual
In the original, all 100 star positions are re-randomized every frame. At 60 fps that’s 6,000 random position calculations per second — most of which are immediately thrown away. More importantly, it means stars move around the screen continuously (a “blizzard” effect rather than a true starfield). Whether that’s a feature or a bug depends on the artist’s intent.
The common fix is to generate positions once in setup(),
store them in an array, and only re-randomize brightness each frame
so stars still twinkle. This is a deliberate visual change,
so it is listed as optional. Discuss with Griffin before including it in
the Stage 2 commit.
This improvement also introduces an array of objects — one of the most powerful patterns in JavaScript. Learn it here; you’ll use it for everything from game sprites to data tables.
OPTION — stable positions, brightness still twinkling// ── Star data (declared here, filled in setup) ────────────────────────────
// Each element will be a plain object: { x, y, size }
// We declare the array outside setup() so drawStars() can also see it.
let stars = [];
// ── setup ─────────────────────────────────────────────────────────────────
function setup() {
createCanvas(CANVAS_W, CANVAS_H);
frameRate(FRAME_RATE);
generateStars(); // run ONCE — positions are fixed from this point on
}
// ── generateStars ─────────────────────────────────────────────────────────
// Builds the stars array. Called only from setup(), never from draw().
// Stars are objects so we can add properties later (e.g., color, speed)
// without rewriting all of drawStars().
function generateStars() {
for (let i = 0; i < STAR_COUNT; i++) {
stars.push({
x: random(CANVAS_W),
y: random(CANVAS_H),
size: random(STAR_SIZE_MIN, STAR_SIZE_MAX)
});
}
}
// ── drawStars (updated version) ───────────────────────────────────────────
// Loops over the pre-built array. Positions are fixed; only alpha is
// re-randomized each frame, creating a gentle twinkle without the drift.
function drawStars() {
noStroke();
for (let star of stars) { // "for...of" is cleaner than index loops
fill(...CLR_STAR, random(STAR_ALPHA_MIN, STAR_ALPHA_MAX));
ellipse(star.x, star.y, star.size, star.size);
}
}
for...of loop is modern, readable JavaScript.
The array-of-objects pattern ([{x,y,size}, {x,y,size}, ...])
scales to hundreds of game sprites, map markers, chat messages, or
database records. Master it here in a context you fully understand,
and you’ll recognize it everywhere in professional code.
Even with excellent function names, a multi-function JS file benefits from visual landmark comments that divide it into major regions. Professional codebases use them universally. They’re especially useful when scrolling quickly through a file you haven’t seen in a while.
AFTER — clear section landmarks (full file structure)/*
* Coder: Griffin + klp
* Date: 2026-04-30
* File: rocketSketch.js
* Purpose: p5.js rocket with twinkling starfield. Stage 2 refactor.
*/
// ============================================================
// CONSTANTS
// ============================================================
const CANVAS_W = 600;
// ... all other constants here ...
// ============================================================
// DATA
// ============================================================
let stars = [];
// ============================================================
// SETUP
// ============================================================
function setup() { ... }
function generateStars() { ... }
// ============================================================
// MAIN DRAW LOOP
// ============================================================
function draw() { ... }
// ============================================================
// HELPER FUNCTIONS — Background & Stars
// ============================================================
function drawBackground() { ... }
function drawStars() { ... }
// ============================================================
// HELPER FUNCTIONS — Rocket
// ============================================================
function drawRocket() { ... }
function drawRocketBody() { ... }
function drawNoseCone() { ... }
function drawPorthole() { ... }
function drawFins() { ... }
function drawFlame() { ... }
=== divider style is easy to scan at a glance.
Some shops use // --- or // ### —
any consistent style works. Pick one, use it in every file, and stick with it.
Consistency is a form of respect for your collaborators (and your future self).
p5.js uses a global drawing state. Every call to
fill(), stroke(), and strokeWeight()
changes the state for everything drawn after it — across functions,
across the current frame, even into the next frame. This is one of the most
common sources of subtle bugs in p5 sketches.
In the original, strokeWeight(2) is set while drawing the body
and silently inherited by the nose cone, porthole, and fins. That works
by accident. When a new function is inserted in between, or the body
drawing is removed, shapes start rendering incorrectly in ways that are very
hard to trace. The fix: every function that cares about stroke explicitly
sets (or clears) it.
// Body sets stroke state once...
fill(180);
stroke(50);
strokeWeight(2);
rect(260, 150, 80, 150);
// Nose cone never calls stroke() or strokeWeight().
// It inherits them from the body — accidentally correct, but fragile.
fill(255, 100, 100);
triangle(260, 150, 340, 150, 300, 100);
// Flame also never calls noStroke().
// It inherits the body's stroke(50), giving the flame an outline.
// (Look at Stage 1's output carefully — there IS an outline on the flame!
// That was probably not intentional.)
noStroke(); // <-- only called here, after the body/nose/porthole have
// already drawn, so they already have a stroke.
fill(255, 150, 0);
triangle(280, 300, 320, 300, 300, 350);
AFTER — every function explicitly declares its needs
function drawRocketBody() {
fill(...CLR_BODY);
stroke(...CLR_STROKE); // explicitly set here
strokeWeight(2); // explicitly set here
rect(BODY_X, BODY_Y, BODY_W, BODY_H);
}
function drawNoseCone() {
fill(...CLR_ACCENT);
stroke(...CLR_STROKE); // re-declared — never assume state from caller
strokeWeight(2);
triangle(BODY_X, BODY_Y, BODY_R, BODY_Y, BODY_CX, NOSE_TIP_Y);
}
function drawFlame() {
noStroke(); // flame intentionally has no outline — stated explicitly
fill(...CLR_FLAME);
triangle(
BODY_X + FLAME_INSET, BODY_BOT,
BODY_R - FLAME_INSET, BODY_BOT,
BODY_CX, BODY_BOT + FLAME_DROP
);
}
✅ Stage 2 Checklist
| # | Task | Key Concept | Visual Change? |
|---|---|---|---|
| 1 | Add file header block | Documentation & professionalism | None |
| 2 | Replace magic numbers with named constants | Readability & maintainability | None |
| 3 | Give all colors meaningful names | Intent clarity & easy retheme later | None |
| 4 | Break draw() into helper functions |
Decomposition & separation of concerns | None |
| 5 | Pre-generate star data in setup() |
Performance & array-of-objects pattern | Slight (optional) |
| 6 | Add section divider comments | Navigation in larger files | None |
| 7 | Each function explicitly sets its drawing state | Encapsulation & bug prevention | None |
🔭 What Comes After Stage 2?
Once the code is clean and well-organized, we’ll be ready to introduce
SketchWaveJS classes (SWDisk, SWLine,
SWPoint, etc.) and apply site-wide styles.
That’s Stage 3 and beyond.
Notice how much easier that transformation will be. Replacing
drawRocketBody() with an SW call is a one-function edit.
Compare that to hunting through 52 tangled lines to figure out which
rect() call is the body (and whether changing it breaks the nose
cone alignment). Refactoring first makes every future stage faster and safer.
That’s the payoff of refactoring: every future change gets easier. The effort is never wasted.