Skip to content

State Persistence & Guard Clauses

Open the editor to try these patterns live.

The Core Rule

Only state.* properties persist across frames. Everything else resets.

Your entry code runs inside an IIFE every frame:

javascript
(() => {
  // YOUR CODE HERE
})()

Top-level let, const, and var live only for that call.

When State Is Cleared

ActionClears state?Clears time?Keeps code?
Frame advancesNoNoYes
PauseNoNo (frozen)Yes
RestartYes → {}Yes → 0Yes
Edit + recompile (~400ms)YesYesYes (new code)
ResetYesYesRestores starter project
Page reloadYesYesDepends on save

After a clear, your if (!state.init) guard runs again on the next frame.

The Guard Clause Pattern

javascript
if (!state.init) {
  state.x = w / 2;
  state.y = h / 2;
  state.vx = 100;
  state.vy = 0;
  state.trail = [];
  state.init = true;
}

state.vy += 200 * dt;
state.x += state.vx * dt;
state.y += state.vy * dt;

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

return {
  particles: [{ x: state.x, y: state.y, r: 10, color: '#8B5CF6' }],
  lines: state.trail.map((p, i) => ({
    x1: state.trail[i - 1]?.x ?? p.x,
    y1: state.trail[i - 1]?.y ?? p.y,
    x2: p.x,
    y2: p.y,
    color: `rgba(139,92,246,${i / state.trail.length * 0.5})`,
    width: 1,
  })),
};

Without the guard, assigning state.x = w / 2 every frame would freeze the particle.

What Happens Without the Guard

javascript
// BUG: resets every frame
let x = 100;
state.x = x;
state.x += 50 * dt;

return {
  particles: [{ x: state.x, y: h / 2, r: 10, color: '#8B5CF6' }],
};

Position never accumulates — it is overwritten each frame.

Alternative: Check for Existence

javascript
if (state.x === undefined) {
  state.x = 100;
  state.vx = 0;
}

Works, but state.init is clearer when initializing many fields at once.

Multi-Object Initialization

javascript
if (!state.init) {
  state.bodies = [
    { x: w * 0.3, y: h * 0.5, vx: 0, vy: -60, mass: 100, r: 15, color: '#F59E0B' },
    { x: w * 0.7, y: h * 0.5, vx: 0, vy: 60, mass: 1, r: 8, color: '#3B82F6' },
  ];
  state.G = 500;
  state.trails = state.bodies.map(() => []);
  state.init = true;
}

Random Init

WARNING

If init uses Math.random(), each Restart (or recompile) produces new initial conditions. Seed or hardcode values when you need repeatable runs.

State as a Dictionary

state is a plain object — any keys work. Prefer flat, descriptive fields over deep nesting that is hard to debug in the HUD.