🚀 Stage 2 Laundry List

Same rocket. Dramatically better JavaScript.

⛽ 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

1 Add a Proper File Header Block

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.

BEFORE — file opens with code, zero context
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);
}
🧑‍🏫 Teacher’s note: In real software teams, code without a header often gets rejected in code review. Some shops require headers by policy. Start the habit now — future employers will notice immediately, and positively.
2 Replace Magic Numbers with Named Constants

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.

BEFORE — cryptic magic numbers scattered everywhere
// 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)
🧑‍🏫 Teacher’s note: Aligning the = 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.
3 Give Colors Meaningful Names

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.

BEFORE — anonymous RGB triplets, intent hidden
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     — orange
AFTER — 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)
🧑‍🏫 Teacher’s note: Notice that the nose cone and fins were already the same color in Stage 1 — but you’d never know without manually counting digits. Now it’s unmistakable: both use 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.
4 Break 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 hidden
function 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
  );
}
🧑‍🏫 Teacher’s note: Count the lines in 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.
5 Bonus Option: Pre-Generate Star Positions in 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);
  }
}
🧑‍🏫 Teacher’s note: The 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.
6 Add Section Dividers in the JS File

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()      { ... }
🧑‍🏫 Teacher’s note: The === 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).
7 Each Function Owns Its Own Drawing State

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.

BEFORE — stroke state bleeds silently across shapes
// 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
  );
}
🧑‍🏫 Teacher’s note: This principle has a name in professional software: encapsulation — the idea that a unit of code is self-contained and doesn’t rely on hidden external state. You’ll see this concept again when you study JavaScript classes, React components, and API design. The pattern is always the same: don’t depend on state you didn’t set yourself.

✅ 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.