Foundation Guide

Particle Life

πŸ“„ 80 Pages 🎯 Stage: See It Work πŸ“¦ Output: ParticleLifeFoundation.jsx

What this page does

Imports the core React library that provides component architecture and rendering capabilities.

Part: Foundation Section: Module Imports Depends on: None

This step is part of the larger process of establishing which tools the application is allowed to use.

Code (this page)

jsx
import React from 'react';

Explanation

  • React is the core library for building user interfaces
  • Required for JSX syntax transformation
  • Provides the component model we'll use throughout
  • No application logic runs yetβ€”this only makes React available

Why this matters

Without this import, the browser cannot interpret JSX syntax or create React components. This single line unlocks the entire React ecosystem for our simulation.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Module not found: 'react' β€” React not installed in project
  • Using require() instead of import (use ES modules)
  • How to recover:

  • Run npm install react if not installed
  • Ensure file has .jsx extension

What this page does

Imports the useRef hook for creating mutable references that persist across renders.

Part: Foundation Section: Module Imports Depends on: Page 1

This step is part of the larger process of importing React hooks for state management.

Code (this page)

jsx
import React, { useRef } from 'react';

Explanation

  • useRef creates a mutable object that persists between renders
  • The .current property can hold any value
  • Changes to refs don't trigger re-renders
  • Essential for animation data that updates every frame

Why this matters

Particle positions change 60 times per second. Using `useState` would cause 60 re-renders per second, killing performance. `useRef` lets us update data without re-rendering.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting curly braces: { useRef }
  • Misspelling as useref (case-sensitive)
  • How to recover:

  • Named imports require curly braces
  • Use exact capitalization: useRef

What this page does

Imports the useEffect hook for running side effects after render.

Part: Foundation Section: Module Imports Depends on: Page 2

This step is part of the larger process of importing React hooks for lifecycle management.

Code (this page)

jsx
import React, { useRef, useEffect } from 'react';

Explanation

  • useEffect runs code after the component renders
  • Used to start animation loops, set up event listeners
  • The return function handles cleanup (stopping animations)
  • Dependency array controls when effect re-runs

Why this matters

Our animation loop needs to start after the canvas exists in the DOM. `useEffect` guarantees the canvas is ready before we try to draw to it.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing comma between imports
  • Duplicate import statements
  • How to recover:

  • Use single import with comma-separated hooks
  • Format: { useRef, useEffect }

What this page does

Imports the useCallback hook for memoizing function references.

Part: Foundation Section: Module Imports Depends on: Page 3

This step is part of the larger process of importing React hooks for performance optimization.

Code (this page)

jsx
import React, { useRef, useEffect, useCallback } from 'react';

Explanation

  • useCallback returns a memoized version of a callback function
  • The function reference stays stable between renders
  • Prevents unnecessary re-creation of functions
  • Critical for animation loops that reference each other

Why this matters

Our `gameLoop`, `updatePhysics`, and `render` functions call each other. Without stable references, React would create new function instances every render, breaking the animation cycle.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • useCallback is not defined β€” missing from import
  • Too many separate import statements
  • How to recover:

  • Add useCallback to the destructured import
  • Combine all hooks in one import statement

What this page does

Declares the fixed width of the simulation canvas in pixels.

Part: Foundation Section: Constants Depends on: Page 4

This step is part of the larger process of defining hardcoded simulation parameters.

Code (this page)

jsx
const WIDTH = 700;

Explanation

  • WIDTH sets the horizontal dimension of our simulation space
  • 700 pixels provides good visibility without being too large
  • Used for canvas size, particle boundaries, and spatial grid
  • Constant defined at module level for easy access

Why this matters

Every part of the simulation needs to know the boundaries. Defining once and reusing prevents inconsistencies between rendering and physics calculations.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Placing inside component function (wrong scope)
  • Using let instead of const
  • How to recover:

  • Define constants before the component function
  • Use const for values that never change

What this page does

Declares the fixed height of the simulation canvas in pixels.

Part: Foundation Section: Constants Depends on: Page 5

This step is part of the larger process of defining hardcoded simulation parameters.

Code (this page)

jsx
const HEIGHT = 700;

Explanation

  • HEIGHT sets the vertical dimension of our simulation space
  • Square canvas (700Γ—700) provides uniform behavior in all directions
  • Particles wrap or bounce at this boundary
  • Matches width for symmetrical simulation

Why this matters

A square simulation space ensures particles behave the same horizontally and vertically. This simplifies physics calculations and creates more natural-looking patterns.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Typo in constant name
  • Different value than WIDTH (unintentional)
  • How to recover:

  • Use exact name: HEIGHT
  • Verify both dimensions match for square canvas

What this page does

Declares the total number of particles in the simulation.

Part: Foundation Section: Constants Depends on: Page 6

This step is part of the larger process of defining hardcoded simulation parameters.

Code (this page)

jsx
const PARTICLE_COUNT = 1500;

Explanation

  • PARTICLE_COUNT determines how many particles exist in the simulation
  • 1500 provides rich emergent behavior without performance issues
  • Particles are distributed evenly across species
  • More particles = more complex patterns, but slower performance

Why this matters

The particle count directly affects both visual complexity and computational cost. 1500 is the sweet spot for interesting behavior at 60fps on most devices.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value too high (performance issues)
  • Using string instead of number
  • How to recover:

  • Start with 1500, adjust based on device performance
  • Use numeric literal without quotes

What this page does

Declares how many distinct particle types (colors) exist in the simulation.

Part: Foundation Section: Constants Depends on: Page 7

This step is part of the larger process of defining hardcoded simulation parameters.

Code (this page)

jsx
const NUM_SPECIES = 6;

Explanation

  • NUM_SPECIES defines how many different particle types exist
  • Each species has unique attraction/repulsion relationships
  • 6 species creates 36 possible interaction pairs (6Γ—6 matrix)
  • More species = more complex rules, more varied behavior

Why this matters

The number of species determines the complexity of the rules matrix. Six species provides enough variety for interesting emergent patterns without overwhelming complexity.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value exceeds COLORS array length (later)
  • Zero or negative value
  • How to recover:

  • Keep between 2 and 8 for best results
  • Must be positive integer

What this page does

Declares the velocity damping factor applied each frame.

Part: Foundation Section: Constants Depends on: Page 8

This step is part of the larger process of defining physics parameters.

Code (this page)

jsx
const FRICTION = 0.5;

Explanation

  • FRICTION multiplies velocity each frame (0.5 = 50% retained)
  • Lower values = more damping, particles slow quickly
  • Higher values = less damping, particles maintain momentum
  • Value between 0 and 1 (exclusive)

Why this matters

Without friction, particles would accelerate infinitely. The friction value controls how "loose" or "tight" the simulation feels. 0.5 provides responsive but controlled movement.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value >= 1 (particles accelerate forever)
  • Value <= 0 (particles stop instantly)
  • How to recover:

  • Keep between 0.1 and 0.99
  • 0.5 is a safe default

What this page does

Declares the maximum distance at which particles can affect each other.

Part: Foundation Section: Constants Depends on: Page 9

This step is part of the larger process of defining physics parameters.

Code (this page)

jsx
const MAX_DISTANCE = 100;

Explanation

  • MAX_DISTANCE sets the range of particle interactions (in pixels)
  • Particles beyond this distance don't affect each other
  • Also used as the cell size for spatial hashing
  • Larger values = longer-range forces, more computation

Why this matters

This value directly impacts both behavior and performance. The spatial hash grid uses this as cell size, so it determines how many neighbors each particle checks.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value too small (no interactions)
  • Value too large (poor performance)
  • How to recover:

  • Keep between 50 and 200 for good results
  • Must be less than canvas dimensions

What this page does

Declares the strength multiplier for all particle interactions.

Part: Foundation Section: Constants Depends on: Page 10

This step is part of the larger process of defining physics parameters.

Code (this page)

jsx
const FORCE_FACTOR = 1;

Explanation

  • FORCE_FACTOR scales all attraction and repulsion forces
  • Higher values = stronger forces, faster movement
  • Lower values = weaker forces, gentler movement
  • Applied to both attraction and repulsion equally

Why this matters

This single multiplier lets us tune the overall "intensity" of the simulation without changing individual rules. Foundation uses 1 (neutral); Configuration will make this adjustable.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value of 0 (no movement)
  • Negative value (inverts all forces)
  • How to recover:

  • Use positive values only
  • 1 is the neutral default

What this page does

Declares the distance below which particles always repel (prevents overlap).

Part: Foundation Section: Constants Depends on: Page 11

This step is part of the larger process of defining physics parameters.

Code (this page)

jsx
const MIN_DISTANCE = 20;

Explanation

  • MIN_DISTANCE defines the "personal space" of particles
  • Particles closer than this always repel, regardless of rules
  • Prevents particles from overlapping or clumping into singularities
  • Creates a soft collision boundary

Why this matters

Without minimum distance repulsion, attractive particles would collapse into a single point. This creates natural spacing and prevents the simulation from degenerating.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Value of 0 (particles can overlap completely)
  • Value >= MAX_DISTANCE (no attraction possible)
  • How to recover:

  • Keep much smaller than MAX_DISTANCE
  • 20 works well with MAX_DISTANCE of 100

What this page does

Declares the color palette used to render different particle species.

Part: Foundation Section: Constants Depends on: Page 12

This step is part of the larger process of defining visual parameters.

Code (this page)

jsx
const COLORS = [
  '#ff6b6b',
  '#4ecdc4',
  '#ffe66d',
  '#95e1d3',
  '#f38181',
  '#aa96da',
  '#fcbad3',
  '#a8d8ea',
];

Explanation

  • COLORS array provides visually distinct colors for each species
  • 8 colors available (more than NUM_SPECIES for flexibility)
  • Hex color codes for canvas rendering
  • Species index maps to array index: COLORS[species]

Why this matters

Distinct colors let users visually track how different species interact. The palette is chosen for good contrast and visibility on dark backgrounds.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing # prefix on colors
  • Fewer colors than NUM_SPECIES
  • How to recover:

  • All hex colors need # prefix
  • Array length must be >= NUM_SPECIES

What this page does

Defines the function that creates individual particle objects.

Part: Foundation Section: Particle System Depends on: Page 13

This step is part of the larger process of building the particle data structure.

Code (this page)

jsx
function createParticle(species) {

Explanation

  • createParticle is a factory function that produces particle objects
  • Takes species parameter (integer 0 to NUM_SPECIES-1)
  • Returns a complete particle object with position and velocity
  • Called once per particle during initialization

Why this matters

Factory functions centralize object creation logic. If we need to change the particle structure, we only modify this one function.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing parameter name
  • Using arrow function inconsistently
  • How to recover:

  • Include species parameter
  • Either style works; be consistent

What this page does

Returns the complete particle object with all required properties.

Part: Foundation Section: Particle System Depends on: Page 14

This step is part of the larger process of building the particle data structure.

Code (this page)

jsx
return {
    x: Math.random() * WIDTH,
    y: Math.random() * HEIGHT,
    vx: 0,
    vy: 0,
    species: species,
  };
}

Explanation

  • x, y: Random position within canvas bounds
  • vx, vy: Initial velocity (zero β€” particles start stationary)
  • species: The species index passed to the factory
  • Math.random() * WIDTH produces value from 0 to WIDTH

Why this matters

Each particle needs position, velocity, and species to participate in the simulation. Starting with zero velocity lets the rules dictate initial movement.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing comma after properties
  • Using width instead of WIDTH
  • How to recover:

  • Each property needs trailing comma (except last)
  • Use the constant names exactly

What this page does

Defines the function that creates the attraction/repulsion rules matrix.

Part: Foundation Section: Rules System Depends on: Page 15

This step is part of the larger process of building the rules matrix.

Code (this page)

jsx
function generateRandomRules(numSpecies) {

Explanation

  • generateRandomRules creates the interaction matrix
  • Takes numSpecies to determine matrix dimensions
  • Returns an object where rules[i][j] is how species i reacts to species j
  • Called once at initialization and on reset

Why this matters

The rules matrix is the heart of particle life. This function generates random rules that create emergent behavior.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Hardcoding species count instead of parameter
  • Missing function keyword
  • How to recover:

  • Accept numSpecies as parameter for flexibility
  • Use function keyword for declaration

What this page does

Creates the empty container object that will hold all species rules.

Part: Foundation Section: Rules System Depends on: Page 16

This step is part of the larger process of building the rules matrix.

Code (this page)

jsx
const rules = {};

Explanation

  • rules is an object that will hold nested objects
  • Structure: rules[speciesA][speciesB] = attractionValue
  • Using object instead of 2D array for cleaner access syntax
  • Will be populated by nested loops

Why this matters

The rules object is the data structure that stores all 36 interaction values (for 6 species). Object syntax makes lookups readable: `rules[0][1]`.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using array [] instead of object {}
  • Defining outside the function
  • How to recover:

  • Use {} for object initialization
  • Keep inside the function body

What this page does

Iterates over each species as the "source" of the interaction.

Part: Foundation Section: Rules System Depends on: Page 17

This step is part of the larger process of building the rules matrix.

Code (this page)

jsx
for (let i = 0; i < numSpecies; i++) {
    rules[i] = {};

Explanation

  • Outer loop iterates i from 0 to numSpecies-1
  • i represents the species that is *reacting* to others
  • rules[i] = {} creates a nested object for this species
  • Each species gets its own set of reactions

Why this matters

The rules matrix has two dimensions: who is reacting (i) and who they're reacting to (j). The outer loop handles the first dimension.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using <= instead of < (off-by-one)
  • Forgetting to initialize rules[i]
  • How to recover:

  • Use < numSpecies not <= numSpecies
  • Must create nested object before assigning to it

What this page does

Iterates over each species as the "target" and assigns random attraction values.

Part: Foundation Section: Rules System Depends on: Page 18

This step is part of the larger process of building the rules matrix.

Code (this page)

jsx
for (let j = 0; j < numSpecies; j++) {
      rules[i][j] = Math.random() * 2 - 1;
    }
  }

Explanation

  • Inner loop iterates j from 0 to numSpecies-1
  • j represents the species being *reacted to*
  • Math.random() * 2 - 1 produces value from -1 to +1
  • Negative = repulsion, Positive = attraction

Why this matters

This creates all 36 interaction rules (6Γ—6). Each value is random, so every simulation produces unique emergent behavior.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using Math.random() alone (only 0 to 1)
  • Wrong formula for range
  • How to recover:

  • Formula Math.random() * 2 - 1 gives -1 to +1
  • Verify with console.log if needed

What this page does

Returns the fully populated rules matrix from the function.

Part: Foundation Section: Rules System Depends on: Page 19

This step is part of the larger process of building the rules matrix.

Code (this page)

jsx
return rules;
}

Explanation

  • Returns the complete rules object
  • Contains all species-to-species interaction values
  • Caller stores this for use in physics calculations
  • Can be regenerated anytime for new random rules

Why this matters

Returning the rules object makes it available to the simulation. The Reset button will call this function again to generate fresh random rules.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Returning before loops complete
  • Returning wrong variable
  • How to recover:

  • return rules must be after both loops close
  • Verify both closing braces are before return

What this page does

Declares the class that optimizes neighbor lookups using spatial partitioning.

Part: Foundation Section: Spatial Optimization Depends on: Page 20

This step is part of the larger process of building the performance optimization system.

Code (this page)

jsx
class SpatialHashGrid {

Explanation

  • SpatialHashGrid divides space into cells for fast neighbor queries
  • Without it, checking every particle against every other is O(nΒ²)
  • With it, we only check particles in nearby cells: O(n)
  • Critical for performance with 1500+ particles

Why this matters

At 1500 particles, O(nΒ²) means 2.25 million distance checks per frame. The spatial hash reduces this to roughly 15,000 checksβ€”a 150x improvement.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing class keyword
  • Lowercase class name (convention is PascalCase)
  • How to recover:

  • Use class SpatialHashGrid
  • Class names should be PascalCase

What this page does

Creates the constructor that initializes the grid with dimensions.

Part: Foundation Section: Spatial Optimization Depends on: Page 21

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
constructor(cellSize, width, height) {
    this.cellSize = cellSize;
    this.width = width;
    this.height = height;
    this.cells = new Map();
  }

Explanation

  • cellSize: Size of each grid cell (typically MAX_DISTANCE)
  • width, height: Canvas dimensions for bounds checking
  • this.cells: Map storing arrays of particles by cell key
  • Map provides fast key-based lookup

Why this matters

The cell size should match MAX_DISTANCE so each cell contains all particles that could possibly interact with each other.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting this. prefix
  • Using object {} instead of Map()
  • How to recover:

  • All instance properties need this.
  • Map provides better performance for frequent clear/set operations

What this page does

Creates the method that empties all cells for the next frame.

Part: Foundation Section: Spatial Optimization Depends on: Page 22

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
clear() {
    this.cells.clear();
  }

Explanation

  • clear() empties the Map of all cell data
  • Called at the start of each physics update
  • Particles are re-inserted with current positions
  • Map's native clear() is very fast

Why this matters

Particles move every frame, so their cell assignments change. Clearing and rebuilding is faster than tracking individual movements.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Calling this.cells = new Map() instead (slower)
  • Missing parentheses on clear()
  • How to recover:

  • Use .clear() method, not reassignment
  • Method calls need parentheses

What this page does

Creates the method that converts coordinates to a cell key string.

Part: Foundation Section: Spatial Optimization Depends on: Page 23

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
getKey(x, y) {
    const col = Math.floor(x / this.cellSize);
    const row = Math.floor(y / this.cellSize);
    return `${col},${row}`;
  }

Explanation

  • Converts x,y position to grid cell coordinates
  • Math.floor ensures consistent cell assignment
  • Returns string key like "3,5" for cell at column 3, row 5
  • String keys work with JavaScript Map

Why this matters

The key uniquely identifies each cell. Particles with the same key are in the same cell and may interact with each other.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using round instead of floor
  • Missing template literal backticks
  • How to recover:

  • Math.floor ensures particles on cell boundary go to lower cell
  • Use backticks for template strings: ` ${col},${row} `

What this page does

Creates the method that adds a particle to its corresponding cell.

Part: Foundation Section: Spatial Optimization Depends on: Page 24

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
insert(particle) {
    const key = this.getKey(particle.x, particle.y);
    if (!this.cells.has(key)) {
      this.cells.set(key, []);
    }
    this.cells.get(key).push(particle);
  }

Explanation

  • Gets the cell key from particle position
  • Creates empty array if cell doesn't exist yet
  • Pushes particle into the cell's array
  • Multiple particles can share the same cell

Why this matters

Inserting all particles into the grid enables fast neighbor lookups. Only particles in nearby cells need to be checked for interactions.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting to create array for new cell
  • Using cells[key] instead of cells.get(key)
  • How to recover:

  • Check has() before first insert to cell
  • Map uses .get() and .set(), not bracket notation

What this page does

Begins the method that retrieves all particles in neighboring cells.

Part: Foundation Section: Spatial Optimization Depends on: Page 25

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
getNearby(x, y) {
    const nearby = [];
    const col = Math.floor(x / this.cellSize);
    const row = Math.floor(y / this.cellSize);

Explanation

  • getNearby finds all particles that might interact with position (x,y)
  • Creates empty array to collect results
  • Calculates the cell coordinates of the query position
  • Will check 3Γ—3 grid of cells around this position

Why this matters

This is the key optimization. Instead of checking all 1500 particles, we only check the ~50-100 particles in nearby cells.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing return statement (added later)
  • Duplicate variable names from other methods
  • How to recover:

  • Each method has its own scope
  • Return will be added after the loops

What this page does

Iterates over the 3Γ—3 grid of cells surrounding the query position.

Part: Foundation Section: Spatial Optimization Depends on: Page 26

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
for (let dx = -1; dx <= 1; dx++) {
      for (let dy = -1; dy <= 1; dy++) {
        const key = `${col + dx},${row + dy}`;
        const cell = this.cells.get(key);
        if (cell) {
          nearby.push(...cell);
        }
      }
    }

Explanation

  • dx and dy iterate from -1 to +1 (9 combinations)
  • Checks the center cell plus all 8 adjacent cells
  • Uses spread operator to add all particles from cell to nearby
  • Skips cells that don't exist (no particles there)

Why this matters

Checking 9 cells ensures we find all particles within MAX_DISTANCE. Particles near cell boundaries could interact with particles in adjacent cells.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using < instead of <= (misses cells)
  • Forgetting null check on cell
  • How to recover:

  • Loop must include -1, 0, and +1 for both dx and dy
  • if (cell) prevents error on empty cells

What this page does

Returns the collected array of nearby particles.

Part: Foundation Section: Spatial Optimization Depends on: Page 27

This step is part of the larger process of building the spatial hash grid.

Code (this page)

jsx
return nearby;
  }
}

Explanation

  • Returns the array containing all particles from 9 neighboring cells
  • Caller will iterate through this array for physics calculations
  • Closing brace ends the getNearby method
  • Final closing brace ends the SpatialHashGrid class

Why this matters

This completes the spatial hash grid. We now have all the tools needed for O(n) neighbor queries instead of O(nΒ²) brute force.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing closing braces
  • Returning before loops complete
  • How to recover:

  • Count braces: method needs one, class needs one
  • Return must be after both loops

What this page does

Declares the main React component that contains the entire simulation.

Part: Foundation Section: Application Shell Depends on: Page 28

This step is part of the larger process of building the React component structure.

Code (this page)

jsx
export default function ParticleLifeFoundation() {

Explanation

  • export default makes this the main export of the module
  • Function component (not class) for hook compatibility
  • Name indicates this is the Foundation stage version
  • All simulation logic will live inside this function

Why this matters

This is the entry point for the entire simulation. React will render this component, which creates the canvas and runs the animation.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting export default
  • Using arrow function (works, but less conventional)
  • How to recover:

  • Include export default for module import
  • Function declaration is clearer for main components

What this page does

Creates a ref to hold the canvas DOM element.

Part: Foundation Section: Application Shell Depends on: Page 29

This step is part of the larger process of setting up refs for animation data.

Code (this page)

jsx
const canvasRef = useRef(null);

Explanation

  • canvasRef will point to the canvas DOM element
  • Initial value is null (element doesn't exist yet)
  • After render, canvasRef.current is the actual canvas
  • Needed to get the 2D rendering context

Why this matters

To draw on the canvas, we need access to its 2D context. The ref provides direct DOM access without querySelector.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Calling outside component function
  • Missing null initial value
  • How to recover:

  • Hooks must be called inside component
  • useRef(null) is the standard pattern for DOM refs

What this page does

Creates a ref to hold the array of all particle objects.

Part: Foundation Section: Application Shell Depends on: Page 30

This step is part of the larger process of setting up refs for animation data.

Code (this page)

jsx
const particlesRef = useRef([]);

Explanation

  • particlesRef.current will hold all 1500 particle objects
  • Initial value is empty array (populated during init)
  • Using ref instead of state prevents re-renders on position changes
  • Particles are modified directly without triggering React updates

Why this matters

State updates trigger re-renders. With 60 FPS animation updating 1500 particles, refs prevent thousands of unnecessary re-renders per second.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using useState instead (performance issue)
  • Initializing with null instead of []
  • How to recover:

  • Always use useRef for high-frequency updates
  • Empty array is safer than null for iteration

What this page does

Creates a ref to hold the rules matrix object.

Part: Foundation Section: Application Shell Depends on: Page 31

This step is part of the larger process of setting up refs for animation data.

Code (this page)

jsx
const rulesRef = useRef({});

Explanation

  • rulesRef.current will hold the attraction/repulsion rules
  • Initial value is empty object (populated during init)
  • Rules change on reset, but not during animation
  • Ref access is faster than state lookup

Why this matters

The rules matrix is accessed thousands of times per frame during physics calculations. Ref access has minimal overhead compared to state.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using wrong initial value type
  • Creating inside a loop or conditional
  • How to recover:

  • Initial value should be {} for object
  • All hooks must be at top level of component

What this page does

Creates a ref to hold the spatial hash grid instance.

Part: Foundation Section: Application Shell Depends on: Page 32

This step is part of the larger process of setting up refs for animation data.

Code (this page)

jsx
const gridRef = useRef(null);

Explanation

  • gridRef.current will hold the SpatialHashGrid instance
  • Initial value is null (created during initialization)
  • Grid is rebuilt every frame (clear + insert all)
  • Single instance reused throughout simulation lifetime

Why this matters

The spatial grid is cleared and rebuilt 60 times per second. Keeping it in a ref avoids recreating the object constantly.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Creating new grid instance here (too early)
  • Using array instead of null
  • How to recover:

  • Grid instance created in init function
  • Null indicates "not yet initialized"

What this page does

Creates a ref to hold the animation frame request ID.

Part: Foundation Section: Application Shell Depends on: Page 33

This step is part of the larger process of setting up refs for animation data.

Code (this page)

jsx
const animationRef = useRef(null);

Explanation

  • animationRef.current stores the ID from requestAnimationFrame
  • Needed to cancel the animation on cleanup
  • Initial value is null (no animation running yet)
  • Updated every frame with new ID

Why this matters

When the component unmounts, we must cancel pending animation frames. Without the ID, we couldn't stop the animation loop.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting this ref (memory leak on unmount)
  • Using wrong variable name in cleanup
  • How to recover:

  • Always store animation IDs for cleanup
  • Match variable names exactly in cleanup code

What this page does

Creates the memoized function that initializes the simulation.

Part: Foundation Section: Initialization Depends on: Page 34

This step is part of the larger process of setting up the simulation.

Code (this page)

jsx
const initSimulation = useCallback(() => {

Explanation

  • initSimulation sets up particles, rules, and grid
  • Wrapped in useCallback for stable function reference
  • Called on mount and when user clicks Reset
  • Empty dependency array means function never changes

Why this matters

useCallback ensures the function identity stays stable. This prevents unnecessary effect re-runs and allows safe use as a dependency.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing useCallback wrapper
  • Using regular function declaration
  • How to recover:

  • Wrap with useCallback(() => { ... }, [])
  • Callback ensures stable reference

What this page does

Initializes the array that will hold all particles and calculates particles per species.

Part: Foundation Section: Initialization Depends on: Page 35

This step is part of the larger process of creating the particle system.

Code (this page)

jsx
const particles = [];
    const perSpecies = Math.floor(PARTICLE_COUNT / NUM_SPECIES);

Explanation

  • particles is the local array we'll populate
  • perSpecies calculates how many particles each species gets
  • Math.floor ensures integer division (1500 / 6 = 250)
  • Total will be 1500 particles evenly distributed

Why this matters

Even distribution ensures each species has equal representation. This creates balanced emergent behavior where no single species dominates.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Not using Math.floor (floating point issues)
  • Declaring variables outside the function
  • How to recover:

  • Always floor division for particle counts
  • Keep initialization logic inside the function

What this page does

Generates all particles distributed evenly across species.

Part: Foundation Section: Initialization Depends on: Page 36

This step is part of the larger process of creating the particle system.

Code (this page)

jsx
for (let species = 0; species < NUM_SPECIES; species++) {
      for (let i = 0; i < perSpecies; i++) {
        particles.push(createParticle(species));
      }
    }

Explanation

  • Outer loop iterates through each species (0 to 5)
  • Inner loop creates perSpecies particles of that type
  • createParticle(species) returns particle with random position
  • Total: 6 species Γ— 250 particles = 1500 particles

Why this matters

Nested loops ensure equal distribution. Each species gets exactly the same number of particles for fair emergent competition.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Off-by-one errors in loop bounds
  • Calling wrong factory function
  • How to recover:

  • Outer: < NUM_SPECIES, Inner: < perSpecies
  • Use createParticle(species) with correct argument

What this page does

Assigns the created particles array to the ref for persistence.

Part: Foundation Section: Initialization Depends on: Page 37

This step is part of the larger process of creating the particle system.

Code (this page)

jsx
particlesRef.current = particles;

Explanation

  • Assigns local array to ref's .current property
  • Ref now holds all 1500 particles
  • This reference persists across renders
  • Physics update accesses particles through this ref

Why this matters

The ref is how we maintain particle state without triggering renders. All physics calculations read and write through `particlesRef.current`.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Assigning to particlesRef instead of .current
  • Forgetting this line (particles lost)
  • How to recover:

  • Always assign to .current property
  • This line is essential for persistence

What this page does

Creates random rules and stores them in the rules ref.

Part: Foundation Section: Initialization Depends on: Page 38

This step is part of the larger process of setting up the rules system.

Code (this page)

jsx
rulesRef.current = generateRandomRules(NUM_SPECIES);

Explanation

  • Calls generateRandomRules to create the interaction matrix
  • Stores result in rulesRef.current
  • Each call generates different random rules
  • Rules persist until next reset

Why this matters

The rules matrix defines all particle behavior. Random rules mean every simulation run produces unique emergent patterns.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Passing wrong argument to generator
  • Assigning to ref instead of .current
  • How to recover:

  • Use NUM_SPECIES constant as argument
  • Assign to .current property

What this page does

Instantiates the spatial hash grid and stores it in the grid ref.

Part: Foundation Section: Initialization Depends on: Page 39

This step is part of the larger process of setting up the optimization system.

Code (this page)

jsx
gridRef.current = new SpatialHashGrid(MAX_DISTANCE, WIDTH, HEIGHT);
  }, []);

Explanation

  • Creates new SpatialHashGrid with cell size = MAX_DISTANCE
  • Passes canvas dimensions for bounds awareness
  • Stores in gridRef.current for physics access
  • Empty dependency array [] means stable function reference

Why this matters

The grid's cell size matching MAX_DISTANCE ensures all interacting particles are in adjacent cells. This is key to the O(n) optimization.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Wrong order of constructor arguments
  • Missing closing }, []) for useCallback
  • How to recover:

  • Order: cellSize, width, height
  • useCallback needs closing with dependency array

What this page does

Creates the memoized function that updates all particle physics.

Part: Foundation Section: Physics Engine Depends on: Page 40

This step is part of the larger process of building the physics system.

Code (this page)

jsx
const updatePhysics = useCallback(() => {
    const particles = particlesRef.current;
    const rules = rulesRef.current;
    const grid = gridRef.current;

Explanation

  • updatePhysics calculates forces and updates positions
  • Wrapped in useCallback for stable reference
  • Extracts current values from refs for cleaner code
  • Local constants avoid repeated .current access

Why this matters

Extracting to local constants improves readability and potentially performance. This function runs 60 times per second.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Accessing refs directly throughout (verbose)
  • Missing useCallback wrapper
  • How to recover:

  • Extract to local constants at function start
  • Always wrap animation functions with useCallback

What this page does

Guards against running physics before initialization completes.

Part: Foundation Section: Physics Engine Depends on: Page 41

This step is part of the larger process of building the physics system.

Code (this page)

jsx
if (!grid) return;

Explanation

  • Checks if grid exists before proceeding
  • Grid is null until initSimulation runs
  • Early return prevents errors on first frame
  • Simple guard pattern for safety

Why this matters

The animation loop starts immediately, but initialization is asynchronous. This guard prevents crashes if physics runs before setup completes.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Checking wrong variable
  • Using === null instead of falsy check
  • How to recover:

  • Check grid (the local variable)
  • !grid handles both null and undefined

What this page does

Clears the grid and re-inserts all particles at current positions.

Part: Foundation Section: Physics Engine Depends on: Page 42

This step is part of the larger process of building the physics system.

Code (this page)

jsx
grid.clear();
    for (const particle of particles) {
      grid.insert(particle);
    }

Explanation

  • clear() empties all cells from previous frame
  • Loop inserts each particle into its current cell
  • Particles may have moved, so cell assignments change
  • This is faster than tracking individual movements

Why this matters

Rebuilding the grid each frame ensures accurate neighbor queries. Particles move constantly, so their cell memberships must update.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting to clear (duplicate particles)
  • Inserting before clearing
  • How to recover:

  • Always clear first, then insert
  • Order matters for correctness

What this page does

Starts the loop that calculates forces for each particle.

Part: Foundation Section: Physics Engine Depends on: Page 43

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
for (const particle of particles) {
      let fx = 0;
      let fy = 0;

Explanation

  • Iterates through all 1500 particles
  • fx, fy accumulate forces for this particle
  • Start at zero, add contributions from neighbors
  • Will be applied to velocity after all forces calculated

Why this matters

Each particle experiences forces from many neighbors. Accumulating into fx/fy collects all contributions before applying.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using let for particle (should be const)
  • Missing force accumulators
  • How to recover:

  • Particle reference doesn't change, use const
  • Forces must accumulate, use let with initial 0

What this page does

Retrieves all particles that could potentially interact with the current one.

Part: Foundation Section: Physics Engine Depends on: Page 44

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
const nearby = grid.getNearby(particle.x, particle.y);

Explanation

  • Queries the spatial grid at particle's position
  • Returns array of particles in 9 neighboring cells
  • Much smaller than full 1500 particles (typically 50-100)
  • This is where the O(n) optimization pays off

Why this matters

Instead of checking all 1500 particles (O(nΒ²)), we only check ~80 nearby particles. This is the key to 60fps performance.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Querying wrong coordinates
  • Not storing result
  • How to recover:

  • Use particle.x and particle.y
  • Store in const nearby for iteration

What this page does

Starts the inner loop that processes each nearby particle.

Part: Foundation Section: Physics Engine Depends on: Page 45

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
for (const other of nearby) {
        if (particle === other) continue;

Explanation

  • Iterates through all nearby particles
  • Skip self-interaction (particle can't affect itself)
  • continue jumps to next iteration
  • Reference equality check is fast

Why this matters

A particle exists in its own cell, so it appears in its own nearby list. We must skip self-interaction to avoid undefined behavior.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using == instead of ===
  • Forgetting self-check (forces explode)
  • How to recover:

  • Use strict equality ===
  • Self-check is essential for stability

What this page does

Computes the direction and distance to the other particle.

Part: Foundation Section: Physics Engine Depends on: Page 46

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
const dx = other.x - particle.x;
        const dy = other.y - particle.y;
        const dist = Math.sqrt(dx * dx + dy * dy);

Explanation

  • dx, dy are the vector from particle to other
  • dist is the Euclidean distance (Pythagorean theorem)
  • Positive dx means other is to the right
  • Distance is always positive

Why this matters

We need both direction (dx, dy) and magnitude (dist) to apply forces. Direction tells us which way to push, distance affects strength.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Subtracting in wrong order
  • Forgetting Math.sqrt
  • How to recover:

  • other - particle points toward other
  • Must sqrt for actual distance

What this page does

Skips particles that are too far or at zero distance.

Part: Foundation Section: Physics Engine Depends on: Page 47

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
if (dist === 0 || dist > MAX_DISTANCE) continue;

Explanation

  • Skip if distance is zero (same position, avoid division by zero)
  • Skip if beyond interaction range
  • Particles in neighboring cells may still be too far
  • continue proceeds to next neighbor

Why this matters

Zero distance would cause division by zero errors. Distance check ensures only truly nearby particles interact.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using < instead of > for range check
  • Forgetting zero check (NaN errors)
  • How to recover:

  • Skip if dist > MAX_DISTANCE
  • Always check dist === 0 first

What this page does

Looks up the attraction/repulsion value for this species pair.

Part: Foundation Section: Physics Engine Depends on: Page 48

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
const rule = rules[particle.species]?.[other.species] || 0;

Explanation

  • Looks up rules[i][j] for this species pair
  • Optional chaining ?. handles missing entries safely
  • Falls back to 0 if rule undefined
  • Value between -1 (repel) and +1 (attract)

Why this matters

The rule determines if these two particles attract or repel. This is the core of particle life's emergent behavior.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Not handling undefined rules
  • Wrong index order
  • How to recover:

  • Use optional chaining and || 0 fallback
  • Order: rules[reactor][target]

What this page does

Computes the strength of the force based on distance and rules.

Part: Foundation Section: Physics Engine Depends on: Page 49

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
let force;

        if (dist < MIN_DISTANCE) {
          force = (dist / MIN_DISTANCE - 1) * 0.5;
        } else {
          const normalizedDist = (dist - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE);
          force = rule * (1 - normalizedDist) * FORCE_FACTOR;
        }

Explanation

  • Two distance zones: close (repel) and medium (rule-based)
  • Close range: always repel, force increases as distance decreases
  • Medium range: rule-based force, strength decreases with distance
  • normalizedDist maps distance to 0-1 range

Why this matters

Close-range repulsion prevents particle overlap. Distance-scaled force creates natural clustering without singularities.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Wrong formula for normalized distance
  • Missing let for force variable
  • How to recover:

  • Normalized: (dist - MIN) / (MAX - MIN)
  • force must be declared with let (assigned conditionally)

What this page does

Adds this neighbor's contribution to the particle's force accumulators.

Part: Foundation Section: Physics Engine Depends on: Page 50

This step is part of the larger process of calculating particle interactions.

Code (this page)

jsx
fx += (dx / dist) * force;
        fy += (dy / dist) * force;
      }

Explanation

  • dx / dist and dy / dist are the unit direction vector
  • Multiplying by force scales to correct magnitude
  • += accumulates contributions from all neighbors
  • Closing brace ends the neighbor loop

Why this matters

Direction comes from the normalized vector, magnitude from force calculation. Accumulating allows multiple neighbors to influence each particle.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using = instead of +=
  • Dividing by wrong value
  • How to recover:

  • Must use += for accumulation
  • Divide by dist to get unit vector

What this page does

Updates particle velocity based on accumulated forces and applies friction.

Part: Foundation Section: Physics Engine Depends on: Page 51

This step is part of the larger process of updating particle state.

Code (this page)

jsx
particle.vx = (particle.vx + fx) * FRICTION;
      particle.vy = (particle.vy + fy) * FRICTION;

Explanation

  • Add force to current velocity
  • Multiply by FRICTION to dampen
  • FRICTION < 1 means velocity decays over time
  • Order matters: add force, then apply friction

Why this matters

Forces change velocity, not position directly. Friction prevents infinite acceleration and creates smooth, natural movement.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Applying friction before force
  • Forgetting friction entirely
  • How to recover:

  • Formula: (v + f) * friction
  • Friction must be applied each frame

What this page does

Caps particle velocity to prevent runaway speeds.

Part: Foundation Section: Physics Engine Depends on: Page 52

This step is part of the larger process of updating particle state.

Code (this page)

jsx
const speed = Math.sqrt(particle.vx * particle.vx + particle.vy * particle.vy);
      const maxSpeed = 5;
      if (speed > maxSpeed) {
        particle.vx = (particle.vx / speed) * maxSpeed;
        particle.vy = (particle.vy / speed) * maxSpeed;
      }
    }

Explanation

  • Calculate current speed (magnitude of velocity)
  • If exceeding max, scale down to maxSpeed
  • Preserves direction while limiting magnitude
  • Closing brace ends the particle force loop

Why this matters

Without speed limits, particles in strong attraction wells could accelerate infinitely. Capping ensures stable, viewable motion.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Not preserving direction when scaling
  • Using wrong speed formula
  • How to recover:

  • Divide by speed, multiply by maxSpeed
  • Speed = sqrt(vxΒ² + vyΒ²)

What this page does

Starts the loop that updates all particle positions.

Part: Foundation Section: Physics Engine Depends on: Page 53

This step is part of the larger process of updating particle state.

Code (this page)

jsx
for (const particle of particles) {
      particle.x += particle.vx;
      particle.y += particle.vy;

Explanation

  • Second loop through all particles
  • Position += velocity (basic kinematics)
  • Separate from force loop for cleaner code
  • All forces calculated before any positions update

Why this matters

Separating force calculation from position update ensures consistent behavior. All particles "see" the same state when calculating forces.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Updating position during force loop
  • Forgetting to add velocity
  • How to recover:

  • Keep loops separate
  • Position changes by += vx/vy

What this page does

Wraps particles that exit one edge to appear on the opposite edge.

Part: Foundation Section: Physics Engine Depends on: Page 54

This step is part of the larger process of handling boundaries.

Code (this page)

jsx
if (particle.x < 0) particle.x += WIDTH;
      if (particle.x >= WIDTH) particle.x -= WIDTH;
      if (particle.y < 0) particle.y += HEIGHT;
      if (particle.y >= HEIGHT) particle.y -= HEIGHT;
    }
  }, []);

Explanation

  • Particles exiting left appear on right (add WIDTH)
  • Particles exiting right appear on left (subtract WIDTH)
  • Same for top/bottom with HEIGHT
  • Creates toroidal (donut-shaped) world

Why this matters

Wrapping creates an infinite world without edges. Patterns can flow continuously without bouncing off walls.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Using > instead of >= for right/bottom
  • Adding instead of subtracting (or vice versa)
  • How to recover:

  • Right edge: >= WIDTH, subtract WIDTH
  • Left edge: < 0, add WIDTH

What this page does

Creates the memoized function that draws the simulation to canvas.

Part: Foundation Section: Rendering Depends on: Page 55

This step is part of the larger process of building the render system.

Code (this page)

jsx
const render = useCallback(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const particles = particlesRef.current;
    const rules = rulesRef.current;

Explanation

  • render draws everything to the canvas
  • Gets canvas element and 2D drawing context
  • Extracts current particles and rules from refs
  • Runs every frame after physics update

Why this matters

Rendering is separate from physics for clean architecture. The render function only reads state, never modifies it.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Getting context before canvas exists
  • Missing .current on refs
  • How to recover:

  • Canvas exists after first render
  • Always access .current property

What this page does

Fills the canvas with a dark background color.

Part: Foundation Section: Rendering Depends on: Page 56

This step is part of the larger process of drawing the frame.

Code (this page)

jsx
ctx.fillStyle = '#0a0a14';
    ctx.fillRect(0, 0, WIDTH, HEIGHT);

Explanation

  • Sets fill color to dark blue-black
  • Fills entire canvas, erasing previous frame
  • Must clear before drawing new positions
  • Dark background makes colored particles visible

Why this matters

Without clearing, particles would leave trails (which could be a feature, but not for Foundation). Each frame starts fresh.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Wrong color format
  • Not covering full canvas
  • How to recover:

  • Use hex color with # prefix
  • fillRect from 0,0 to WIDTH,HEIGHT

What this page does

Renders each particle as a colored circle at its current position.

Part: Foundation Section: Rendering Depends on: Page 57

This step is part of the larger process of drawing the frame.

Code (this page)

jsx
for (const particle of particles) {
      ctx.fillStyle = COLORS[particle.species % COLORS.length];
      ctx.beginPath();
      ctx.arc(particle.x, particle.y, 3, 0, Math.PI * 2);
      ctx.fill();
    }

Explanation

  • Loop through all particles
  • Set color based on species (modulo handles overflow)
  • arc draws a circle: center, radius 3, full circle
  • fill renders the circle solid

Why this matters

This is where the simulation becomes visible. Each particle is a small colored dot, and together they form emergent patterns.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting beginPath (paths connect)
  • Wrong arc parameters
  • How to recover:

  • Always beginPath before each shape
  • Arc: x, y, radius, startAngle, endAngle

What this page does

Renders a small visualization of the rules matrix in the corner.

Part: Foundation Section: Rendering Depends on: Page 58

This step is part of the larger process of providing visual feedback.

Code (this page)

jsx
const cellSize = 12;
    const matrixX = 10;
    const matrixY = HEIGHT - NUM_SPECIES * cellSize - 40;

    ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
    ctx.fillRect(
      matrixX - 5,
      matrixY - 20,
      NUM_SPECIES * cellSize + 25,
      NUM_SPECIES * cellSize + 35
    );

    ctx.fillStyle = '#fff';
    ctx.font = '10px monospace';
    ctx.fillText('Rules Matrix:', matrixX, matrixY - 5);

Explanation

  • Position matrix in bottom-left corner
  • Semi-transparent black background for readability
  • White label text above the matrix
  • Matrix shows how species interact visually

Why this matters

The rules matrix helps users understand why particles behave as they do. Green = attract, red = repel.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Position calculation off-screen
  • Wrong coordinate order in fillRect
  • How to recover:

  • Verify Y puts matrix above bottom edge
  • fillRect: x, y, width, height

What this page does

Renders the colored cells showing attraction/repulsion values.

Part: Foundation Section: Rendering Depends on: Page 59

This step is part of the larger process of visualizing the rules.

Code (this page)

jsx
for (let i = 0; i < NUM_SPECIES; i++) {
      ctx.fillStyle = COLORS[i];
      ctx.fillRect(matrixX, matrixY + (i + 1) * cellSize, cellSize - 2, cellSize - 2);
      ctx.fillRect(matrixX + (i + 1) * cellSize, matrixY, cellSize - 2, cellSize - 2);

      for (let j = 0; j < NUM_SPECIES; j++) {
        const rule = rules[i]?.[j] || 0;
        const r = rule < 0 ? Math.floor(-rule * 255) : 0;
        const g = rule > 0 ? Math.floor(rule * 255) : 0;

        ctx.fillStyle = `rgb(${r}, ${g}, 50)`;
        ctx.fillRect(
          matrixX + (j + 1) * cellSize,
          matrixY + (i + 1) * cellSize,
          cellSize - 2,
          cellSize - 2
        );
      }
    }

Explanation

  • Row/column headers show species colors
  • Inner cells show rule values as colors
  • Red = negative (repulsion), Green = positive (attraction)
  • Blue component (50) prevents pure black for zero

Why this matters

The color-coded matrix provides instant visual feedback about the rules governing the simulation.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Color calculation produces invalid values
  • Cell positions overlap
  • How to recover:

  • Ensure r, g are 0-255 integers
  • cellSize - 2 creates gap between cells

What this page does

Renders informational text showing particle count and species.

Part: Foundation Section: Rendering Depends on: Page 60

This step is part of the larger process of providing visual feedback.

Code (this page)

jsx
ctx.fillStyle = '#fff';
    ctx.font = '14px monospace';
    ctx.fillText(`Particles: ${particles.length}`, 10, 20);
    ctx.fillText(`Species: ${NUM_SPECIES}`, 10, 40);
  }, []);

Explanation

  • White text for visibility on dark background
  • Monospace font for consistent character width
  • Shows particle count and species count
  • Position at top-left corner

Why this matters

The HUD provides context about the simulation parameters. Users can verify the correct number of particles is active.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Template literal syntax error
  • Missing closing for useCallback
  • How to recover:

  • Use backticks for template: ` text ${var}
  • }, [])` closes the useCallback

What this page does

Creates the main animation loop that drives physics and rendering.

Part: Foundation Section: Animation Loop Depends on: Page 61

This step is part of the larger process of running the simulation.

Code (this page)

jsx
const gameLoop = useCallback(() => {
    updatePhysics();
    render();
    animationRef.current = requestAnimationFrame(gameLoop);
  }, [updatePhysics, render]);

Explanation

  • gameLoop runs physics then rendering each frame
  • requestAnimationFrame schedules next frame at ~60fps
  • Stores animation ID for cleanup
  • Dependencies ensure fresh function references

Why this matters

This is the heart of the simulation. The loop runs continuously, updating physics and drawing the result 60 times per second.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing dependencies in array
  • Calling gameLoop() instead of passing reference
  • How to recover:

  • Include [updatePhysics, render] in deps
  • Pass gameLoop without parentheses to requestAnimationFrame

What this page does

Creates the effect that initializes and starts the simulation.

Part: Foundation Section: Lifecycle Depends on: Page 62

This step is part of the larger process of managing component lifecycle.

Code (this page)

jsx
useEffect(() => {
    initSimulation();
    animationRef.current = requestAnimationFrame(gameLoop);

Explanation

  • Effect runs after component mounts
  • Calls initSimulation to set up particles, rules, grid
  • Starts the animation loop
  • First frame begins rendering immediately

Why this matters

useEffect ensures the canvas exists before we try to draw. Initialization happens once on mount.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Calling gameLoop() instead of passing to requestAnimationFrame
  • Forgetting initSimulation call
  • How to recover:

  • Pass function reference, not result
  • Init must run before animation starts

What this page does

Returns the cleanup function that stops animation on unmount.

Part: Foundation Section: Lifecycle Depends on: Page 63

This step is part of the larger process of managing component lifecycle.

Code (this page)

jsx
return () => {
      cancelAnimationFrame(animationRef.current);
    };
  }, [initSimulation, gameLoop]);

Explanation

  • Return function runs when component unmounts
  • cancelAnimationFrame stops the pending frame request
  • Prevents memory leaks and errors after unmount
  • Dependencies include all functions used in effect

Why this matters

Without cleanup, the animation would continue after component removal, causing errors and memory leaks.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing return statement
  • Wrong dependency array
  • How to recover:

  • Cleanup must be returned from effect
  • Include [initSimulation, gameLoop]

What this page does

Creates the handler function for the Reset button.

Part: Foundation Section: Control Handlers Depends on: Page 64

This step is part of the larger process of user interaction.

Code (this page)

jsx
const handleReset = useCallback(() => {
    initSimulation();
  }, [initSimulation]);

Explanation

  • handleReset regenerates the simulation
  • Calls initSimulation to create new particles and rules
  • Wrapped in useCallback for stable reference
  • Dependency on initSimulation ensures correct function

Why this matters

The Reset button lets users generate new random rules without refreshing the page. Each reset creates unique emergent behavior.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Forgetting useCallback wrapper
  • Missing dependency
  • How to recover:

  • Wrap in useCallback for button stability
  • Include [initSimulation] dependency

What this page does

Starts the JSX return that defines the component's rendered output.

Part: Foundation Section: UI Composition Depends on: Page 65

This step is part of the larger process of building the user interface.

Code (this page)

jsx
return (
    <div className="flex flex-col items-center gap-4 p-4 bg-gray-900 min-h-screen">

Explanation

  • return ( begins the JSX output
  • Outer div is the main container
  • Tailwind classes: flex column, centered, gaps, padding
  • Dark background matching canvas aesthetic

Why this matters

The container div establishes the layout structure. Flexbox centering keeps the simulation visually balanced.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing opening parenthesis
  • Tailwind not available
  • How to recover:

  • JSX needs return ( with parenthesis
  • Tailwind classes work in supported environments

What this page does

Renders the simulation title at the top of the interface.

Part: Foundation Section: UI Composition Depends on: Page 66

This step is part of the larger process of building the user interface.

Code (this page)

jsx
<h1 className="text-2xl font-bold text-white">
        Particle Life Simulation
      </h1>

Explanation

  • h1 is semantically correct for main title
  • White text for contrast on dark background
  • Bold weight for emphasis
  • Text size appropriate for header

Why this matters

The title identifies the simulation and establishes visual hierarchy. Semantic HTML improves accessibility.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing closing tag
  • Wrong class syntax (className vs class)
  • How to recover:

  • JSX uses className not class
  • Ensure tag is properly closed

What this page does

Renders the canvas element where the simulation draws.

Part: Foundation Section: UI Composition Depends on: Page 67

This step is part of the larger process of building the user interface.

Code (this page)

jsx
<canvas
        ref={canvasRef}
        width={WIDTH}
        height={HEIGHT}
        className="border border-gray-700 rounded"
      />

Explanation

  • ref={canvasRef} connects to our ref for drawing access
  • width and height set canvas resolution
  • Border provides visual boundary
  • Self-closing tag (no children)

Why this matters

The canvas is where all rendering happens. The ref connection is essential for the render function to access the drawing context.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing ref connection
  • Using style instead of width/height attributes
  • How to recover:

  • ref={canvasRef} is required
  • Canvas dimensions must be attributes, not CSS

What this page does

Renders the button that resets the simulation with new rules.

Part: Foundation Section: UI Composition Depends on: Page 68

This step is part of the larger process of building the user interface.

Code (this page)

jsx
<button
        onClick={handleReset}
        className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
      >
        Reset (New Rules)
      </button>

Explanation

  • onClick={handleReset} connects to our handler
  • Blue button with hover effect
  • Descriptive label explains what reset does
  • Transition makes hover smooth

Why this matters

The Reset button is the primary user interaction. It lets users explore different rule sets without reloading.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Calling handler immediately: onClick={handleReset()}
  • Missing handler function
  • How to recover:

  • Pass reference without parentheses
  • Ensure handleReset is defined above

What this page does

Renders helper text explaining how to use the simulation.

Part: Foundation Section: UI Composition Depends on: Page 69

This step is part of the larger process of building the user interface.

Code (this page)

jsx
<p className="text-gray-400 text-sm text-center max-w-md">
        Watch particles self-organize based on attraction and repulsion rules.
        Each color represents a species with unique interactions.
        Click Reset to generate new random rules.
      </p>

Explanation

  • Gray text for secondary information
  • Smaller font size for helper content
  • Centered and max-width for readability
  • Three sentences explain the simulation

Why this matters

Instructions help new users understand what they're seeing. Clear explanation increases engagement with the simulation.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Text too long (wrapping issues)
  • Wrong color class
  • How to recover:

  • max-w-md constrains width
  • text-gray-400 for muted appearance

What this page does

Closes the JSX return and component function.

Part: Foundation Section: UI Composition Depends on: Page 70

This step is part of the larger process of completing the component.

Code (this page)

jsx
</div>
  );
}

Explanation

  • Closes the outer container div
  • Closes the return statement parenthesis
  • Closes the component function
  • Component is now complete

Why this matters

Proper closing ensures valid JSX and JavaScript syntax. The component is now ready to render.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Mismatched tags/braces
  • Missing semicolon (optional but conventional)
  • How to recover:

  • Count opening and closing tags
  • Verify all braces match

What this page does

Provides a verification checklist for the completed Foundation stage.

Part: Foundation Section: Verification Depends on: All previous pages

This step is part of the larger process of ensuring correctness.

Code (this page)

jsx
// Complete Foundation code should include:
// - 4 React imports (React, useRef, useEffect, useCallback)
// - 8 constants (WIDTH, HEIGHT, PARTICLE_COUNT, etc.)
// - createParticle factory function
// - generateRandomRules function
// - SpatialHashGrid class with 5 methods
// - ParticleLifeFoundation component with:
//   - 4 refs (canvas, particles, rules, grid, animation)
//   - initSimulation, updatePhysics, render, gameLoop functions
//   - useEffect for lifecycle
//   - handleReset handler
//   - JSX with canvas and reset button

Explanation

This checklist summarizes all code that should exist after completing Foundation.

Why this matters

Verification ensures nothing was missed. A complete Foundation is the base for Configuration stage.

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:

  • Missing function or constant
  • Incorrect hook dependencies
  • How to recover:

  • Compare against the checklist
  • Review each section's pages

What this page does

Confirms particles are created correctly on startup.

Part: Foundation Section: Verification Depends on: Page 72

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Particles all in one corner (initialization bug)
  • Wrong particle count (loop error)
  • Missing colors (species assignment)
  • How to recover:

  • Check createParticle random ranges
  • Verify loop bounds in initSimulation
  • Confirm COLORS array has enough entries

What this page does

Confirms the physics simulation runs correctly.

Part: Foundation Section: Verification Depends on: Page 73

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • No movement (physics not running)
  • Jerky motion (performance issue)
  • Particles disappear (wrapping bug)
  • How to recover:

  • Verify gameLoop is calling updatePhysics
  • Check spatial grid is working
  • Review boundary wrapping logic

What this page does

Confirms the rules visualization renders correctly.

Part: Foundation Section: Verification Depends on: Page 74

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Matrix off-screen (position calculation)
  • All cells same color (rule lookup error)
  • Missing headers (loop range)
  • How to recover:

  • Check matrixY calculation
  • Verify rules[i][j] access
  • Confirm loop starts at correct index

What this page does

Confirms the Reset button works correctly.

Part: Foundation Section: Verification Depends on: Page 75

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Nothing happens (handler not connected)
  • Animation stops (gameLoop interrupted)
  • Same rules every time (random seed issue)
  • How to recover:

  • Verify onClick={handleReset}
  • Ensure initSimulation doesn't stop animation
  • Math.random() should vary naturally

What this page does

Confirms the simulation runs at acceptable frame rates.

Part: Foundation Section: Verification Depends on: Page 76

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Low frame rate (spatial grid not working)
  • Memory growth (leaking objects)
  • GPU bottleneck (too many draw calls)
  • How to recover:

  • Verify spatial grid reduces neighbor checks
  • Ensure particles array isn't growing
  • Canvas operations are efficient

What this page does

Confirms Foundation does NOT include Configuration-stage features.

Part: Foundation Section: Verification Depends on: Page 77

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Accidentally added Configuration features early
  • Imported code from later stages
  • How to recover:

  • Remove any settings UI
  • Foundation uses hardcoded constants only

What this page does

Confirms Foundation does NOT include Persistence-stage features.

Part: Foundation Section: Verification Depends on: Page 78

This step is part of the verification process.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, stop and review before continuing.

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues:

  • Accidentally added persistence early
  • Third-party library storing data
  • How to recover:

  • Remove any storage code
  • Foundation is entirely transient

What this page does

Confirms Foundation stage is fully complete and ready for Configuration.

Part: Foundation Section: Completion Depends on: All previous pages

This is the final page of the Foundation Guide.

Explanation

Why this matters

βœ“ Checkpoint

After this page, you should be able to:

After this page, you should be able to:

If this is not true, review the relevant pages before continuing.

If this is not true, stop and review before continuing.

Contents