I’m thinking of using the Mystic Shield as a lecture series for teaching Processing in JavaScript. Learners would be relatively new to programming. I want to create a version 3 where the final sketch code is well-organized, documented, and follows professional standards. With that in hand, I can back-figure what to teach students as they get started. What are your recommendations?
The right mental model here is two audiences, one file. The v3 sketch serves the finished learner who can read and understand all of it, and it simultaneously serves you as the curriculum architect who can read it and immediately see what to teach first, second, and third. That dual purpose only works if the documentation is genuinely useful — not just comments that restate the code, but comments that explain the why, name the concept, and give a learner something to hold onto.
I have seven concrete recommendations for what makes v3 different from v2. They fall into three categories: how the file is organized, how the code is documented, and how the teaching signal is embedded in the structure itself. After the recommendations, I’ll lay out the full backward-designed teaching progression that the v3 structure makes possible.
v2 has no file header. v3 opens with a block comment that names the sketch, its version, the author, the date, the canvas size, the color mode, and what a reader needs to understand to work with the file. This is the first thing a new programmer reads, and it should answer the question “what is this file for?” in four lines or fewer. The header should also name the one central p5.js concept the sketch demonstrates: layered drawing with independent rotation.
v2 has three constants at the top. v3 should have five named sections of constants, each preceded by a one-line comment that says what the section controls:
- Canvas —
CANVAS_W,CANVAS_H. Naming these means changing canvas size never requires a text search. - Animation —
MANDALA_SPEED,STAR_SPEED(negative = counter-clockwise). The sign of the star speed is currently a silent magic number; naming it communicates intent. - Geometry — all ring radii, satellite count, satellite orbit radius, satellite sizes, star size. Currently every number is raw; naming them means a student can find “what controls the satellite size” in one place.
- Color palette — one constant per visual element
with a plain-English comment describing what it looks like
(
RING_OUTER_HUE = 33— “burnt orange”). The palette comment block becomes a direct teaching moment about HSB. - Spark behavior —
SPARK_COUNT, speed range, size range, fade range. Currently these are anonymous literals scattered through the class.
colorMode() explainer commentHSB is invisible to beginners unless it is named. In v3, the
colorMode(HSB, 360, 100, 100, 100) call should be preceded by a
four-line comment block that explains the four parameters in plain English:
Hue (0–360 degrees around the color wheel), Saturation (0 = grey,
100 = full color), Brightness (0 = black, 100 = full light), Alpha
(0 = invisible, 100 = opaque). That comment turns one API call into
one of the most important learning moments in the file.
Every function in v3 gets a one-to-three line block comment before it that
answers three questions: what does this function draw or do, what coordinate
system is active when it runs (canvas origin vs. translated center vs. a
pushed rotation context), and what named constants it depends on. This comment
is not for experts — it is for a student trying to locate where
“the ring in the middle” is drawn. The draw() function
gets the most important comment in the file: a numbered list of the drawing
layers in order, explaining that later layers draw on top of
earlier ones, and why the sparks must come before the rings.
draw() layer order commentThe single most important concept in any animated sketch is the drawing order.
In v2, draw() has no comments at all. In v3, the function opens with
a block that reads like a painter’s instruction list:
- Background — clears the previous frame; everything after this is fresh
- Sparks — drawn first so the rings appear in front of them
- Mandala rings — the slowly rotating concentric structure
- Star array — drawn last, counter-rotating, appears on top of rings
Each of the four push/pop blocks in draw()
gets an inline comment naming the layer. A student reading v3 will understand
z-order before they understand push().
The Spark class in v2 is correct but opaque. In v3:
- The class header explains the lifecycle in one sentence: a spark is born near the center, travels outward, fades as it goes, and resets when it reaches the edge.
- The constructor comment lists each property and what it controls.
reset()gets a comment noting it is called both on construction (via the constructor) and on expiry (viaupdate()) — this is the non-obvious part that trips up beginners.update()names the lifecycle check explicitly: if radius exceeds canvas boundary, restart this spark.display()explains themap()call in plain English: the farther from center, the lower the alpha —map()converts the radius into a 0–1 life ratio, then multiplies by 100 to get the HSB alpha value. This is the trickiest math in the sketch and deserves the most comment.
The satellite sigil loop and the Spark display() method both
use polar → Cartesian conversion
(x = r × cos(θ), y = r × sin(θ)).
In v2 this appears without explanation. In v3, the first time this pattern
appears it gets a comment: polar coordinates — convert (radius, angle)
to (x, y) on the Cartesian plane. The satellite loop reinforces this by
naming the variable orbitAngle rather than just i *
(TWO_PI / count), making the geometry readable.
| Stage | What the student builds | New concept introduced |
|---|---|---|
| 0 | Blank canvas with dark background | createCanvas(), setup(), draw(), background() — the animation loop exists even when nothing moves |
| 1 | One white ellipse in the corner | ellipse(x, y, w, h), pixel coordinates, the top-left origin |
| 2 | Colored ellipse with no fill | stroke(), fill(), noFill(), RGB color — and why we call these before the shape, not after |
| 3 | Same ellipse, now described in HSB | colorMode(HSB, 360, 100, 100, 100) — hue as an angle on the color wheel; saturation and brightness as percentages; why HSB is easier than RGB for “make this more orange” |
| 4 | Ellipse centered on the canvas | translate(width/2, height/2) — moving the origin to the center so (0, 0) becomes the middle; why symmetric designs need a center origin |
| 5 | Ellipse that slowly rotates | rotate(angle), radians vs. degrees, incrementing an angle variable each frame — and why the rotation appears to affect the whole canvas |
| 6 | Outer ring + inner ring, each with independent rotation | push() / pop() — saving and restoring state so each layer can rotate independently without corrupting the next |
| 7 | Eight satellite sigils evenly spaced on an orbit | for loop, TWO_PI / count for even angular spacing, polar coordinates: x = r × cos(θ), y = r × sin(θ) |
| 8 | Counter-rotating interlocked star on top | Multiple independent angle variables, a second push()/pop() layer, negative rotation speed, QUARTER_PI for 45° offset, rectMode(CENTER) |
| 9 | One spark: a dot that moves outward from center and disappears | Introducing a class — constructor, properties, methods; update() for logic, display() for drawing; why we separate the two |
| 10 | One spark that fades as it travels | map() — converting one range to another; using radius as a proxy for age; multiplying the life ratio by 100 to get HSB alpha |
| 11 | 300 sparks: the full particle system | Array of class instances, for...of loop, random initial radius so sparks stagger their start; why a particle system is just a for loop over a class |
| 12 | Complete Mystic Shield v3 | Integration: the draw() layer order (background → sparks → rings → star), reading and using all named constants, understanding the full file as a system |
With the seven recommendations applied, every teaching stage maps to a named
section or function in v3. A student who wants to understand Stage 7 (the
satellite loop) can find the Geometry constants section and the
drawMysticShield() function header and read exactly
what those constants control. A student who wants to understand Stage 10 (the
fade) can find the Spark behavior constants section and the
display() method comment explaining the
map() call.
This is what “professional documentation as a teaching tool” means: the structure of the file is the curriculum outline. You do not need to explain the sketch in a separate document because the sketch explains itself.
Let’s go ahead and implement mysticShield3.html with its
associated files that will implement the final version from which we will build
the teaching sequence.
Two files were created: the sketch src/MysticShield3Sketch.js and the
showcase page mysticShield3.html. The HTML reuses
styles/mysticShield2Styles.css and
scripts/mysticShield2Scripts.js from v2 — the visual design and
code-display utilities are identical; only the sketch source changes.
For the code display, mysticShield3.html uses the
fetch() approach rather than a duplicated inline
SKETCH_CODE string. The v3 sketch is ~230 lines with all its comments;
having the page fetch the file it is already running avoids the maintenance problem
of keeping two copies in sync.
| File | Purpose |
|---|---|
src/MysticShield3Sketch.js |
The v3 sketch — five constant sections, function header comments, draw() layer-order comment, Spark class lifecycle documentation, polar coordinate comment |
mysticShield3.html |
Showcase page — same two-column layout as v2; intro card explaining what v3 adds; spark bar links to chatlog, v2 comparison, cross training |
v2 embeds the full sketch source as a String.raw template literal in the
HTML <head>. For v3, the sketch is ~230 lines with all its teaching
comments — duplicating that in the HTML creates a maintenance hazard: any change to
the sketch would require updating both the JS file and the HTML string. The
fetch() approach loads the already-running sketch file as text for the
greenbar display, keeping exactly one copy of the source.
v3 produces the same visual as v2. It shares mysticShield2Styles.css
(all fire/amber color tokens, greenbar theme, canvas panel, page nav) and
scripts/mysticShield2Scripts.js (buildCodeDisplay(),
copyRaw(), applyKW/applyNM). Creating separate
v3 style and script files would duplicate 300+ lines for no visual gain. When a future
version needs distinct styling, a v3-specific stylesheet can be added at that point.
The Session 1 plan named the spark birth constants SPARK_BIRTH_MIN and
SPARK_BIRTH_MAX. During implementation these were renamed to
SPARK_BIRTH_NEAR and SPARK_BIRTH_FAR. The MIN/
MAX naming is correct for a speed or size range, but birth radius
communicates spatial distance — “near” and “far” are
more descriptive for that concept and align with the way the Spark comments explain
the lifecycle (“born near the center”). The map() call in
display() uses SPARK_BIRTH_NEAR as its lower bound, making
the intent immediately clear.
In line 118 you used a parameter: P2D —
createCanvas(CANVAS_W, CANVAS_H, P2D). What was the benefit
of this incorporation? Could it have been left out? I’ve never had
to use it before.
P2D is p5.js’s explicit name for its default 2D Canvas renderer.
p5.js supports two renderers:
P2D— the HTML5 Canvas 2D API; every sketch we have written at TNT uses this.WEBGL— the WebGL 3D renderer; required forbox(),sphere(), lighting, and camera operations.
If you omit the third argument entirely, p5.js defaults to P2D —
so yes, it could have been left out with zero functional difference.
Every sketch you have ever written that calls createCanvas(w, h) is
already running P2D silently.
The reason to include it explicitly in a teaching file is visibility. A
student who later encounters a 3D sketch calling
createCanvas(w, h, WEBGL) will immediately understand the contrast,
because v3 showed them the parameter exists. It also makes the choice searchable:
if a student asks “why does my 3D sketch look different?” they can
grep for WEBGL vs P2D across files.
P2D as a documentation choice, not a requirementIncluding P2D is a style/pedagogy decision, not a correctness one.
The rule of thumb: in a learning-focused file, make the default explicit when knowing
the default exists is itself a teaching moment. In production code, omit it —
the shorter signature is idiomatic p5.js for 2D sketches.
| Call | Renderer | Notes |
|---|---|---|
createCanvas(800, 600) | P2D (implicit) | Idiomatic for 2D; what every prior TNT sketch uses |
createCanvas(800, 600, P2D) | P2D (explicit) | Pedagogically clear; no functional difference |
createCanvas(800, 600, WEBGL) | WebGL 3D | Required for 3D primitives, lighting, and camera |
Version 2 of Mystic Shield is a good representation of our objectives for
comparing the various Processing languages. I was very pleased with version 3,
a clear upgrade from version 2. Can you create
mysticShield3CTraining.html that features the upgrade in the
other two languages? Use the same model you used for version 2. This way,
I have a professional end goal for each of the three languages.
Four files were created or updated. MysticShield3.pde and
MysticShield3.py translate the v3 professional standard into Java
Processing and Python Processing respectively, preserving all five named constant
sections and all function header comments. mysticShield3CTraining.html
follows the exact layout model of mysticShield2CTraining.html —
same color-keyed paper, same three-panel side-by-side structure, same
buildCodeDisplay() and copyRaw() utilities — but
adds a seventh concept card specifically about named constants and how each
language expresses immutability. mysticShield3.html was also updated
to link to the new App Comparison page instead of v2’s, and its spark bar
now matches the consistent navigation pattern established for the other pages.
The note card at the bottom of the v3 App Comparison page explicitly identifies
the three language-specific differences that v3 makes visible: const
vs final type vs UPPER_CASE convention; sz vs
size in the Java Spark class; and global in Python
for module-level angle variables modified inside draw().
| File | Action | Purpose |
|---|---|---|
src/MysticShield3.pde |
Created | Java Processing v3 — five named constant sections, function header comments, final type declarations, pushMatrix()/popMatrix(), sz field in Spark class |
src/MysticShield3.py |
Created | Python Processing v3 — UPPER_CASE constant convention, snake_case throughout, global keyword for angle variables, pushMatrix()/popMatrix() |
mysticShield3CTraining.html |
Created | v3 App Comparison page — 7 concept cards (adds “Named Constants”), three color-keyed code panels, v3 source download bar, note card comparing const/final/UPPER_CASE across languages |
mysticShield3.html |
Updated | Spark bar and pageNav updated: downloads removed, “Cross Training” renamed “App Comparison” pointing to new v3 page, ← Creative Cross Training back link added |
The v2 App Comparison has six concept cards. v3 earns a seventh: how each language declares an immutable named value. This is one of the most transferable ideas in software engineering and the core of what v3 adds to v2.
| Language | Syntax | Enforcement |
|---|---|---|
| JavaScript | const CANVAS_W = 800; | Runtime: reassignment throws a TypeError |
| Java | final int CANVAS_W = 800; | Compile-time: reassignment is a compile error; type must be declared |
| Python | CANVAS_W = 800 | Convention only: UPPER_CASE signals intent; Python does not enforce it |
sz field instead of sizeIn the Java Spark class, the dot diameter field is named sz rather
than size. Processing’s global size() function
is a method name in the Java environment; declaring a field named size
does not cause a compile error but can confuse students who expect
size(800, 600) to still refer to the canvas setup call. Using
sz eliminates the ambiguity. The v3 Spark class header comment
documents this decision explicitly. The JavaScript and Python versions retain
this.size and self.size because neither environment
has this naming conflict.
The full MysticShield3Sketch.js file includes detailed multi-line
function header blocks and an HSB explainer comment block (about 230 lines total).
In the v3 App Comparison panels, the code strings use the section-divider structure
and key per-line comments but compress the verbose header blocks to single-line
summaries. This keeps the three panels comparable in height while preserving the
essential named-constant and painter’s-algorithm documentation. The full
source files (.pde and .py) and the v3 showcase page
(mysticShield3.html) carry the complete documentation.
size() Error — The settings() FixThere was an error in the v3 Java file: size() cannot be used here.
We did not have that problem with the v2 Java file. I think the numbers have
to be literals, not variables, if I’m not mistaken. Can you fix the
problem?
You’re right about the restriction. Processing’s Java mode parses
size() specially before handing the file to the Java compiler —
it needs to know the canvas dimensions at preprocessing time to generate the
PApplet subclass. When size() appears in
setup() with anything other than integer literals, the preprocessor
rejects it with “size() cannot be used here”. That is
why the v2 file worked: it used size(800, 600, P2D) directly.
The clean fix is not to fall back to literals — that defeats the point
of having CANVAS_W and CANVAS_H. Processing 3+
introduced a settings() method that is called before
setup(), specifically to allow dynamic or named-constant sizing.
In settings(), size() can reference
variables and constants. So the fix is to move the size() call
into a new settings() method and remove it from
setup().
settings() replaces size() in setup()Processing executes the sketch lifecycle in this order:
settings() → setup() → draw().
Only settings() may call size(), fullScreen(),
or smooth() with non-literal arguments. This is a Processing 3+
design: the renderer must be initialized before setup() runs.
| Method | When called | Can size() use named constants? |
|---|---|---|
settings() | Before setup(), before renderer exists | Yes — designed for this |
setup() | After renderer is initialized | No — preprocessor rejects non-literals |
The v2 Java file was unaffected because it used literal integers
(800, 600) in setup(). v3 introduced
CANVAS_W and CANVAS_H — which is exactly the right
professional practice — but exposed this Processing constraint for the first time.
The fix is a two-line change: add settings(), remove size()
from setup().
void settings() {
size(CANVAS_W, CANVAS_H, P2D); // named constants allowed here
}
void setup() {
colorMode(HSB, 360, 100, 100, 100);
frameRate(60);
for (int i = 0; i < SPARK_COUNT; i++) {
sparks.add(new Spark());
}
}
Files updated: src/MysticShield3.pde and the
JAVA_CODE string in mysticShield3CTraining.html.
We have version 2 featured on our Creative Cross Training home page. Version 3 is obviously better. I’m thinking we should feature version 3 there instead, although showing a lesser version and growing it to a better one is a valid strategy too. Your perspective?
Feature v3. The CCT home is a gallery landing page — a first impression. Visitors should see the best work. The evolution story (v2 → v3) is already told excellently within the Mystic Shield pages themselves: v3 opens with “same visual as v2, every number now named,” the chatlog documents all sessions, and the App Comparison page links to v2 for direct comparison. The progression is preserved — it just doesn’t need to start on the gallery homepage.
The “show lesser and grow it” strategy is compelling in a
classroom sequence, and that’s already what
mysticShield3.html does with its 12-stage chatlog. The CCT home
is upstream of that — it’s where people arrive, not
where they start the lesson.
The Entry #1 card in creativeCrossTrainingHome.html was updated:
- Card click now navigates to
mysticShield3.html(v3 showcase) - Card description updated: “v3 replaces every magic number with a named constant … v2 is available for before–and–after comparison”
- Concept label updated to Entry #1 — v3 Edition
- Footer links reordered: v3 showcase • v3 app comparison • v2 (compare) • interactive • movie clip
The rationale: the CCT home is a first impression, not a teaching sequence. The v2 → v3 progression story is embedded in the Mystic Shield pages themselves (chatlog, intro card on v3.html, App Comparison note card). Featuring v3 on the gallery home shows best work without losing the evolution narrative.
The explanation in the DWR & Eureka log about our settings() problem was so
helpful. I think we should mention it in the Mystic Shield v3 App Comparison document.
If you agree, let’s add it — and either way, let’s document this
discussion in our chatlog for v3.
Agreed. The DWR entry explains the why behind settings() —
specifically that named constants are the reason it becomes necessary at all, and that
combining size() in setup() with an explicit
settings() produces the “duplicate” error. That context is exactly
what a student reading the App Comparison code panels needs. Three changes were made:
- DWR Entry #34 received a stable anchor
id="dwr-34"so the App Comparison page can link directly to it. - The note card at the bottom of
mysticShield3CTraining.htmlwas updated with a direct link to DWR #34, noting that named constants made the constraint surface for the first time in v3. - This exchange was documented as Session 7 in the chatlog.
The note card already mentioned that Java requires settings() for named-constant
sizing. Adding the DWR cross-reference turns that sentence into a gateway to the full explanation:
why the preprocessor imposes this restriction, what “duplicate settings()”
actually means, and how v3’s professional standard — replacing magic numbers with named
constants — is what causes the constraint to surface. The DWR entry is the right place for
that depth; the App Comparison note card is the right place for the link.
The anchor id="dwr-34" was added to the DWR table row so the link
(../../dwrEureka.html#dwr-34) lands directly on the entry rather than
dropping the reader at the top of a long page.