Fixed-scroll arcade shooter. Ship, glider shots, enemy waves, still-life terrain — all live cells in a Conway Immigration simulation (2-team B3/S23) running in a WebGL2 fragment shader. Contact is majority-birth and survival under the rules.
Glider = projectile. LWSS = heavy (orthogonal c/2). Pulsar = ship (period-3 oscillator). The sim stamps weapons and enemies as team-colored patterns. → One Bit Short of a War — why classic Life can’t host a fight, and how one extra bit turns it into a war. → One Grain Per Cell Event — every birth/death as one audio grain.
The simulation is the game
Health is pattern integrity — tagged ship body vs the pulsar’s expected live-count for the current phase. Death is decoherence sustained past GOL flicker.
GPU owns a 2048×512 toroidal grid as ping-pong RGBA8 textures (R alive, G team,
B age, A player tag). A generation is three fullscreen passes: translate
(tagged cells shift rigidly; vacated cells clear), inject (shots/spawns/
terrain from a pattern atlas as scissored quads, split on torus wrap), step
(Immigration via texelFetch with wrap):
if (wasAlive && (alive == 2 || alive == 3)) {
outColor = vec4(1.0, self.g, min(self.b + 1.0/255.0, 1.0), self.a); // survive, age
} else if (!wasAlive && alive == 3) {
float team = (playerParents >= 2) ? TEAM_PLAYER : TEAM_ENEMY; // majority of 3 parents
float tag = (taggedParents >= 2) ? 1.0 : 0.0;
outColor = vec4(1.0, team, 0.0, tag); // born
} else {
outColor = vec4(0.0); // die
}
Translate does not exempt the ship — the pulsar survives because it sustains itself under B3/S23. Damage is pattern perturbation.
Health returns to the CPU narrowly: every 30 ticks a shader reduces the ship’s
48×48 ROI to 1×1 (tagged-live in R, enemy-live in G). Readback is async —
readPixels into a PBO, fence sync, non-blocking clientWaitSync(…, 0, 0) poll,
at most one in flight. CPU sends small pattern uploads up and two reduced bytes
down — never the grid.