Skip to content

Cheatsheet

One-page reference. Open the editor, paste into main.js, then click Restart.

Globals

NameUse
timeElapsed sim seconds
dtFrame delta (≤ 0.05)
statePersist anything here
w, hCanvas size (CSS px)
state.clickX/YLast click; set to undefined after use
keys.*Key held? keys.ArrowUp, keys.w, keys[' '] (true/false)
ai.*Behavioral AI — import ai from 'ai', then call ai.agent, ai.think, ...

Skeleton

javascript
if (!state.init) {
  // setup once
  state.init = true;
}

// integrate with dt
// handle clicks / keys

return {
  particles: [],
  lines: [],
  vectors: [],
  circles: [],
  arcs: [],
  bars: [],
  text: {},
};

Primitives (defaults)

KeyRequiredDefaults
particlesx, yr: 6, color: '#8B5CF6' · optional label
linesx1,y1,x2,y2color: '#ffffff', width: 2, dashed: false
vectorsx, y (components)origin (0,0), color: '#22C55E' · optional ox,oy,label
circlescx,cy,rstroke rgba(255,255,255,0.2) · optional fill
arcscx,cy,r,startAngle,endAnglecolor: '#F59E0B', width: 2
barsx,y,w,hcolor: '#8B5CF6' · optional label
textkey → string/numberHUD top-right, insertion order

Draw order: lines → circles → arcs → bars → vectors → particles → text.

Coordinates

  • Origin top-left; Y down
  • Center: const cx = w / 2, cy = h / 2
  • Angles: 0 right, π/2 down

Must-know rules

  1. Persist with state.* — not top-level let
  2. Always multiply motion by dt
  3. Cap growing arrays (trail, particles)
  4. Consume clicks after handling
  5. Modules: module.exports + named import; pass dt/state as args
  6. Edit recompiles (~400ms) and clears state/time
  7. keys tracks held keys - check keys.ArrowUp, keys.w, etc. (see Keyboard Input)

Controls

ButtonEffect
PauseFreeze loop
RestartClear state/time, keep code
ResetRestore starter project

Snippets

Euler

javascript
state.vx += ax * dt;
state.vy += ay * dt;
state.x += state.vx * dt;
state.y += state.vy * dt;

Floor bounce

javascript
if (state.y > h - 20) {
  state.y = h - 20;
  state.vy *= -0.8;
}

Trail

javascript
state.trail.push({ x: state.x, y: state.y });
if (state.trail.length > 200) state.trail.shift();

Click spawn

javascript
if (state.clickX !== undefined) {
  state.particles.push({ x: state.clickX, y: state.clickY, vx: 0, vy: 0 });
  state.clickX = undefined;
  state.clickY = undefined;
}

AI agent — seek the cursor

javascript
import ai from 'ai';

if (!state.p) {
  state.p = ai.agent({ x: 100, y: 100, maxSpeed: 180, maxForce: 90,
    brain: (me, s) => ai.seek(me, s.mouse.x, s.mouse.y, 90) });
}
ai.think(state.p, ai.sense(state.p, w, h, time), dt);
ai.wrap(state.p, w, h);
return { particles: [{ x: state.p.x, y: state.p.y, r: 8, color: '#8B5CF6' }] };

AI flock — 40 boids (see the AI cookbook)

javascript
import ai from 'ai';

if (!state.flock) {
  state.flock = Array.from({ length: 40 }, () => ai.agent({
    x: Math.random() * w, y: Math.random() * h, maxSpeed: 110, maxForce: 55,
    brain: (me, s) => ai.flock(me, ai.neighbors(state.flock, me, 55),
      { perception: 55, sep: 1.6, ali: 1, coh: 1, maxForce: 55 }),
  }));
}
for (const b of state.flock) { ai.think(b, ai.sense(b, w, h, time), dt); ai.wrap(b, w, h); }
return { particles: state.flock.map((b) => ({ x: b.x, y: b.y, r: 4, color: '#8B5CF6' })) };