📂 The Stage 3 Files
Stage 3 takes the Stage 2 orbit — which already looked great — and reorganizes the code without changing a single pixel of output. The goal: apply the ORB pattern so the sketch is easier to read, easier to debug, and ready for the SketchWave object model in future stages. Three files carry all the logic:
sketches/jacksonOrbit3Sketch.js —
All constants, animation state, and drawing functions live here.
initMyDesign() populates stars and land masses once.
drawMyDesign(ctx) is the per-frame entry point.
sketches/jacksonOrbitBase3Sketch.js —
Handles setup() and draw(), creates the canvas,
wires the control panel, and calls your hooks
(initThemeColor, initMyDesign,
drawMyDesign) at the right moments.
| File | Who touches it | Stage 3 changes |
|---|---|---|
jacksonOrbit3.html |
No one — just loads scripts in order | Script tags updated to Stage 3 sketch files |
sketches/jacksonOrbit3Sketch.js |
Student (Jackson) | ORB refactor: named constants block, two-phase draw, initMyDesign() hook |
sketches/jacksonOrbitBase3Sketch.js |
Template (minimal changes) | Star-field loop moved out; now calls initMyDesign() instead |
fetch(url).then(r => r.text()).
💡 The ORB Pattern
Stage 3 introduces three coding habits called the ORB pattern. The orbit looks identical to Stage 2 — only the internal structure changes. Together these three habits are a stepping stone toward the SketchWave object model.
O — Organize: Named Constants at the Top
Stage 2 was already much better than Stage 1, but it still had
inline fractions scattered throughout the drawing functions:
width * 0.25, angle * 0.30, d * 0.12.
When you see 0.30 in the middle of drawMoon() you
have to mentally trace “what is 30% of?” every single time.
The fix: hoist every meaningful fraction to a named constant at the top of the file, grouped under clear comment banners. The drawing functions then read like a mission-control panel.
| ❌ Stage 2 — inline fractions | ✅ Stage 3 — named constants |
|---|---|
drawEarth(width * 0.25);translate(width * 0.4, 0);rotate(angle * 0.3);let numLands = 15;
|
const EARTH_SCALE = 0.25;const MOON_ORBIT_RADIUS = 0.40;const MOON_ORBIT_FACTOR = 0.30;const LAND_COUNT = 15;… then in draw: drawEarth(width * EARTH_SCALE);translate(width * MOON_ORBIT_RADIUS, 0);rotate(angle * MOON_ORBIT_FACTOR);
|
The payoff: tuning the animation means changing one number in the constants block at the top. No hunting through a dozen function bodies. The constant name also documents what the number means, for free.
R — Render, Then Update: the Two-Phase Draw Loop
In Stage 2, drawMyDesign() called drawing functions and
advanced state (angle += 0.01, star.x -= star.speed)
interleaved inside the same helper functions. This makes it hard to reason
about what the frame looks like at any given moment.
The fix: split drawMyDesign() into two explicit
phases. Phase 1 renders. Phase 2 updates state. Every Phase-1 function sees a
consistent “snapshot” of the world for the entire frame.
| ❌ Mixed render + update | ✅ Two explicit phases |
|---|---|
// inside drawStars():ellipse(star.x, star.y, star.size);star.x -= star.speed; // state mutation!if (star.x < 0) star.x = width;// at end of drawMyDesign():angle += ORBIT_SPEED;
|
function drawMyDesign(ctx) { // Phase 1: Render (back → front) drawStars(); drawEarth(width * EARTH_SCALE); drawMoon(width * MOON_SCALE); drawStation(); // Phase 2: Advance state earthRotation += EARTH_ROTATION_SPEED; angle += ORBIT_SPEED;}
|
Why it matters: when every draw function is pure read (it reads state but never writes it), you can move them around, comment one out, or add a new layer anywhere in Phase 1 without worrying about accidentally changing the animation speed or position.
Notice that in Stage 3, drawStars() still moves stars
(star.x -= star.speed) inside the loop for simplicity.
The R principle is applied at the top level of
drawMyDesign, where it has the most impact. Perfection
at every level is a future stage.
B — Bootstrap: One-Time Init in initMyDesign()
In Stage 2, the star-field was populated inside setup() of the
base template file. The land-mass algorithm was called from inside
drawEarth() using a per-frame guard
(if (landMasses.length === 0) {…}).
Both approaches scatter initialization logic away from the data it initializes.
The fix: add one new hook — initMyDesign()
— in the user sketch file. The base template calls it once from
setup() after the canvas is ready. The result: all one-time work
lives next to the data it sets up.
| ❌ Guard in a draw function | ✅ One-time hook called from setup() |
|---|---|
// inside drawEarth() — runs every frame!if (landMasses.length === 0) { initLandMasses(r);}// stars built in base template's setup():for (let i = 0; i < 500; i++) { stars.push({…});}
|
// in jacksonOrbit3Sketch.js:function initMyDesign() { for (let i = 0; i < STAR_COUNT; i++) { stars.push({…}); } const r = (width * EARTH_SCALE) / 2; initLandMasses(r);}// in jacksonOrbitBase3Sketch.js setup():if (typeof initMyDesign === 'function') initMyDesign();
|
The bonus: the Reset button can now fully restart the
animation by clearing the arrays and calling initMyDesign()
again. With a per-frame guard that was impossible — the guard would
just fire on the very next frame.
📁 Why Two Sketch Files?
You may have noticed that Stage 3 loads two JavaScript files before running the sketch:
sketches/jacksonOrbit3Sketch.js— loaded firstsketches/jacksonOrbitBase3Sketch.js— loaded second
The load order matters. The base template reads constants declared in the
user sketch (ULx, ULy, fr,
DEFAULT_BG_HEX…) during its own initialization.
If the base template loaded first, those constants would be
undefined and the canvas would break.
The separation of concerns: the student file is
a creative workspace — constants, colors, drawing functions.
The base template is infrastructure — canvas creation,
control-panel wiring, the p5.js setup() and draw()
lifecycle. Students can do everything they need in one file and never touch
the other.
This pattern — a user file plus a framework file — is exactly
the direction SketchWave is heading. In a future stage, the “base
template” role will be absorbed by the SketchWave class library
and individual shape classes (SWDisk, SWColor, etc.).
📜 Source Code
Loading jacksonOrbit3Sketch.js…
Loading jacksonOrbitBase3Sketch.js…
Loading jacksonOrbit3.html…
💪 Try It Yourself
Use the Copy buttons above to grab the student sketch file
(jacksonOrbit3Sketch.js), then try these ORB-focused challenges:
-
O — Tune the Moon orbit —
find
MOON_ORBIT_RADIUSin the constants block at the top ofjacksonOrbit3Sketch.js. Change it from0.40to0.55and reload. Notice how the Moon jumps outward without you touching a single draw function. That’s the whole point of named constants. -
O — Speed up the station —
find
ORBIT_SPEEDand change it from0.01to0.04. The Moon automatically speeds up too (it usesangle * MOON_ORBIT_FACTOR). Why? Because they share the sameanglestate. -
R — Comment out Phase 2 —
in
drawMyDesign(), comment out bothearthRotation += EARTH_ROTATION_SPEED;andangle += ORBIT_SPEED;and reload. The scene renders perfectly but nothing moves. This proves that Phase 1 (render) and Phase 2 (update) are truly independent. -
B — Change the star count —
find
STAR_COUNT = 500and change it to1500. Reload and press Start. Notice how the Reset button regenerates all 1500 stars — becauseinitMyDesign()is called from the Reset handler, not just fromsetup(). -
B — Break the load order —
in
jacksonOrbit3.html, swap the two<script>tags so the base template loads before the user sketch. Reload. What error appears in the browser console? Swap them back. What does this tell you about why load order matters in a two-file template design?
⚠️ The Copy button requires a secure context
(https:// or localhost). Viewing from a local file:// URL?
Right-click the Run link and choose “View Page Source” instead.