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:
(() => {
// YOUR CODE HERE
})()Top-level let, const, and var live only for that call.
When State Is Cleared
| Action | Clears state? | Clears time? | Keeps code? |
|---|---|---|---|
| Frame advances | No | No | Yes |
| Pause | No | No (frozen) | Yes |
| Restart | Yes → {} | Yes → 0 | Yes |
| Edit + recompile (~400ms) | Yes | Yes | Yes (new code) |
| Reset | Yes | Yes | Restores starter project |
| Page reload | Yes | Yes | Depends on save |
After a clear, your if (!state.init) guard runs again on the next frame.
The Guard Clause Pattern
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
// 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
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
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.