# One Kernel, Every Universe

*Task 5 of 5 · [Cellular Automata](https://gpu.rocks/learn/cellular-automata-407c2c34.md) · GPU.js Learn*

B3/S23 is one point in a whole family. Any *outer-totalistic* rule is fully
described by two 9-entry tables: `born[n]` — does a dead cell with n live
neighbors come alive? — and `stay[n]` — does a live cell with n survive? Conway
is `born[3] = 1`, `stay[2] = stay[3] = 1`, zeros everywhere else.
**HighLife** adds `born[6] = 1` and suddenly the world contains a
pattern that builds copies of itself.

Here's the move that matters: pass the tables **as kernel arguments**.
The rulebook stops being code and becomes data — one compiled kernel runs every universe
in the family, and switching physics is just passing different arrays. No `if`
per rule, no recompile: alive cells look up `stay[count]`, dead cells look up
`born[count]`.

## Goal

**Goal:** finish the `evolve` kernel so it applies whatever
rule tables it's handed — then let the wired-up code count where Life and HighLife disagree
about the same world's next tick.

## Requirements

- The kernel takes `world`, `born` and `stay` — don't hard-code any rule
- Dead cells (`self === 0`) return `born[count]`
- Live cells (`self === 1`) return `stay[count]`

## Hint 1 — arrays index like anywhere else

`count` is a number from 0 to 8, and `born` is a 9-entry
array — `born[count]` is already the answer for a dead cell. The lookup
*is* the rule.

## Hint 2 — a single return

```js
let fate = born[count];
if (self === 1) fate = stay[count];
return fate;
```

## Same idea elsewhere

Shipping small lookup tables to a fixed kernel instead of recompiling is how GPUs
stay fast when behavior changes: CUDA and ROCm keep them in `__constant__`
memory, WebGPU and Metal bind them as uniform buffers — same shader, new physics, zero
pipeline rebuilds.

## Starter code

```js
// The rulebook as data: born[n] and stay[n] answer every question
// a cell can ask. One kernel, any Life-like universe.
const gpu = new GPU({ mode });

const evolve = gpu.createKernel(function (world, born, stay) {
  let count = 0;
  for (let dy = -1; dy < 2; dy++) {
    for (let dx = -1; dx < 2; dx++) {
      const yy = (this.thread.y + dy + 16) % 16;
      const xx = (this.thread.x + dx + 16) % 16;
      count += world[yy][xx];
    }
  }
  const self = world[this.thread.y][this.thread.x];
  count -= self;
  // TODO: no rule logic — just look the answer up.
  // Dead cells consult born[count]; live cells consult stay[count].
  return self;
}, { output: [16, 16] });

// The same world, two different laws of physics:
const life = await evolve(world, lifeBorn, lifeStay);
const high = await evolve(world, highlifeBorn, lifeStay);

let differ = 0;
for (let y = 0; y < 16; y++) {
  for (let x = 0; x < 16; x++) {
    if (life[y][x] !== high[y][x]) differ++;
  }
}
console.log('Life and HighLife disagree on ' + differ + ' cells after one tick');
```

---

Interactive version: https://gpu.rocks/learn/cellular-automata-407c2c34/5

[Previous task](https://gpu.rocks/learn/cellular-automata-407c2c34/4.md)
