Stage 5 merges the sinusoid hover from Stage 4 with the Perlin-noise eye animation introduced
in Eye 11. The ghost now floats and its pupils wander on their own —
no one is controlling anything. A Control Panel on the right side of the
canvas lets you tune all the interesting parameters live.
Big Idea 1 — The Rebuild Problem
Every time the ghost moves, setPosition() calls _build(), which
creates brand-new SWEyeball instances. Without intervention, that would also
reset _noiseT to zero and re-randomize _noiseOffsetX/Y —
the eyes would jump chaotically every frame while the sinusoid is running.
The fix lives in swGhost.js _build(): before creating new eye instances it
saves the noise clock and offsets; after construction it restores them, exactly like it
already saved/restored the pupil polar offsets. The eyes never know a rebuild happened.
Big Idea 2 — Eye Animation Priority Chain
Three automation modes coexist. Each frame, draw() walks down this chain and
stops at the first match:
- Drag (highest priority) —
handleMouseDragged() has
already moved the pupil; skip all automation for that eye.
- Follow ON — both eyes rotate toward the cursor. Track ON shares
a midpoint angle (perfectly synchronized); Track OFF lets each eye aim from its own
center, producing a natural vergence difference.
- lookingAround — the idle default. Active whenever Follow is OFF.
Track ON mirrors the left eye’s Perlin output to the right; Track OFF gives each
eye a fully independent wander path.
Big Idea 3 — Control Panel Parameters
| Control |
What it changes |
Range / default |
| Follow | Eyes track the cursor on the canvas | ON / OFF |
| Track | Both eyes share the same gaze direction | ON / OFF |
| Look Strength | How far pupils travel from center (lookingAround amplitude, fraction of max offset) | 0–1, default 0.8 |
| Noise Speed | How fast the Perlin clock advances each frame, setting the tempo of the wander | 0.001–0.025, default 0.005 |
| Iris Color | Color of the iris ring on both eyes | color picker |
| Iris Width | Stroke weight of the iris ring on both eyes (calls setIrisThickness()) | 1–8 px, default 6 |
| Animate | Toggle sinusoid hover on/off (SPACE key also works) | ON / OFF |
| H / V Amplitude | How far the ghost drifts horizontally / vertically (user units) | 0–1.5, defaults 0.20 / 0.30 |
| H / V Period | How many seconds per full horizontal / vertical cycle | 1–15 s, defaults 5 / 3 |
| Ghost Color | Fill color for the head, body, and toe bumps (calls setGhostColor()) | color picker, default orange |
| Num Toes | Number of toe-bump disks along the ghost’s base (calls setNumToes()); 0 = flat bottom | 0–8, default 4 |
| BG Color | Canvas background color | color picker, default #ececec |
| BG Opacity | Background fill opacity each frame; lower values let ghost trails linger as after-image streaks | 0–100, default 100 |
Big Idea 4 — Track × Follow Interaction Matrix
| Track | Follow | Result |
| OFF | OFF | Each eye wanders independently (default) |
| ON | OFF | Both eyes wander in sync (mirrored noise) |
| OFF | ON | Each eye aims from its own center → vergence / “wonky” |
| ON | ON | Perfect shared midpoint aim → robotic precision |
Big Idea 5 — Incommensurable Periods
The default horizontal period (5 s) and vertical period (3 s) share no common factor below
their LCM of 15, so the combined flight path takes 15 seconds to repeat exactly.
Changing both sliders to the same value produces a perfectly diagonal shuttle —
a satisfying way to see the math. Setting them to very close but unequal values produces
a slow-rotating ellipse (Lissajous-like behavior).