Cheatsheet
One-page reference. Open the editor, paste into main.js, then click Restart.
Globals
| Name | Use |
|---|---|
time | Elapsed sim seconds |
dt | Frame delta (≤ 0.05) |
state | Persist anything here |
w, h | Canvas size (CSS px) |
state.clickX/Y | Last 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)
| Key | Required | Defaults |
|---|---|---|
particles | x, y | r: 6, color: '#8B5CF6' · optional label |
lines | x1,y1,x2,y2 | color: '#ffffff', width: 2, dashed: false |
vectors | x, y (components) | origin (0,0), color: '#22C55E' · optional ox,oy,label |
circles | cx,cy,r | stroke rgba(255,255,255,0.2) · optional fill |
arcs | cx,cy,r,startAngle,endAngle | color: '#F59E0B', width: 2 |
bars | x,y,w,h | color: '#8B5CF6' · optional label |
text | key → string/number | HUD 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:
0right,π/2down
Must-know rules
- Persist with
state.*— not top-levellet - Always multiply motion by
dt - Cap growing arrays (
trail, particles) - Consume clicks after handling
- Modules:
module.exports+ namedimport; passdt/stateas args - Edit recompiles (~400ms) and clears
state/time keystracks held keys - checkkeys.ArrowUp,keys.w, etc. (see Keyboard Input)
Controls
| Button | Effect |
|---|---|
| Pause | Freeze loop |
| Restart | Clear state/time, keep code |
| Reset | Restore 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' })) };