CellDrift は、 セルラー オートマトン シミュレーター。 Conway + 4 ルール バリアント (HighLife / Day&Night / Seeds / Replicator) × 12 古典パターン。 ラボ 36本目、 シミュレーター thesis 4本目 (PilePark 物理 / DoodleDrop お絵描き物理 / FracCast 数学イテレーション / CellDrift セルラー進化、 「物理 / 描く / 数 / 細胞」 4 本柱完成)。
なぜこの形にしたか
直近 5本 (glyph-forge / soroban-rush / pop-drift / maya-cast / type-rush) に シミュレーターゼロ、 §3.1 連続にならず safe。 thesis 分布で シミュ / 占い / データ が 3 と薄め、 シミュから補充。
候補比較:
- Conway ライフゲーム + 5 ルール (採用) — wow 中 + SEO 中、 純 JS 軽量
- WebGPU 流体 — wow 超強だが SEO 弱 + Safari 17.4+ 制約
- Gray-Scott 反応拡散 — wow 強だが SEO ニッチすぎ
- 育休給付金 (計算 5) — SEO 強だが マネー計算機 4 連続直近で食い合いリスク
選んだ理由:
- Conway は 数学・計算理論の古典 で 50 年連続的検索ボリューム (大学 / 高校 数学 / プログラミング 学習)
- 5 ルール バリアント で wow を上乗せ (Replicator B1357/S1357 = 全パターンが自己複製は SNS 拡散性高)
- 12 古典パターン ライブラリ で 「Glider が無限放出される Gosper Gun」 等の名所をワンクリック
- 純 JS + Canvas 2D + Uint8Array で 軽量、 240×160 grid (38,400 cells) × 60 fps で快適
- visual 「連続フォーム用紙 + dot-matrix プリンタ motif」 が既存 35 本柱と完全別軸
visual direction — §6.1 Visual Audit 21本目の適用
直近 5本の visual を書き出す:
glyph-forge — ink + 朱赤 + 生成り + brass plate + emerald (letterpress 活字工房)
soroban-rush — warm brown + 玉青藍 + 玉朱 + 和紙 (そろばん 寺子屋)
pop-drift — cream paper + cobalt + warm red (昭和統計年鑑 Risograph)
maya-cast — earth red + jade + obsidian + gold + bone (マヤ古代壁画)
type-rush — cream paper + carbon ribbon + brass key (古いタイプライター)
題材 「Conway / セルラー / 数学」 → motif 候補:
- 連続フォーム用紙 + dot-matrix プリンタ (緑バー + cream + ink + perf 穴) ← 採用
- 80s CRT phosphor green — ascii-bake と被る
- 生物学 ペトリ皿 顕微鏡 — maya-cast / fate-num と色温度近い
- 黒板チョーク — doodle-drop と被る
採用 3 要素:
- palette: 緑バー paper
#d6e8d0+ cream paper#f4ecd5(交互) + dot-matrix ink#1a1410+ alert red#c92e2e+ perf 穴 brown#6a4a28+ emerald accent#2d7d5e - 緑系は doodle-drop / ascii-bake / side-tax と一部重なるが、 「緑バー / cream 交互行 + perf 穴」 の 連続フォーム用紙 motif は唯一
- motif: continuous form paper (緑バー / cream 交互 22px 横縞、 14px 緑 + 22px cream の繰り返し + 左右 32px perf 列 (12 個の dot-matrix 穴) + ink banner header 「CELL-DRIFT · MAINFRAME GRID · v1.0」 + RULE: B3/S23 表記 + dashed border)
- typography: JetBrains Mono (dot-matrix 印字、 stats + chip + 全体的 mainframe terminal 感) + Space Grotesk 800 (display) + 漢数字ステップ (壱/弐/参)
技術スタック
Grid + Uint8Array ping-pong (2 バッファ swap)
export function makeGrid(w: number, h: number): Grid {
return { w, h, cells: new Uint8Array(w * h) }
}
// 毎フレーム step() で nextGrid に書き込み → swap でメモリ再利用
const g = gridRef.current
const n = nextRef.current
step(g, n, rule, wrap)
gridRef.current = n
nextRef.current = g // swap
240×160 grid = 38,400 cells。 各セル 1 byte (0/1) の Uint8Array で メモリ効率最大。 ping-pong で alloc 不要、 GC 圧迫なし。 60 fps × 38,400 cells = 2.3M cells/sec で 8-近傍カウント = 18M ops/sec、 V8 で楽勝。
5 ルールを Bn/Sn データで宣言
export const RULES: RuleInfo[] = [
{ id: "conway", spec: "B3/S23", born: set(3), survive: set(2, 3) },
{ id: "highlife", spec: "B36/S23", born: set(3, 6), survive: set(2, 3) },
{ id: "daynight", spec: "B3678/S34678", born: set(3,6,7,8), survive: set(3,4,6,7,8) },
{ id: "seeds", spec: "B2/S", born: set(2), survive: set() },
{ id: "replicator", spec: "B1357/S1357", born: set(1,3,5,7), survive: set(1,3,5,7) },
]
// 1 つの step 関数で 全ルール対応
if (alive && rule.survive.has(n)) next = 1
else if (!alive && rule.born.has(n)) next = 1
else next = 0
Set<number> による born/survive 集合判定で O(1) 検査。 5 ルール = データ宣言だけ、 ロジックは共通。 新ルール追加は 1 行で OK。
12 古典パターンを 2D 配列で内蔵
export const PATTERNS: Pattern[] = [
{
id: "gosper",
label: "Gosper Glider Gun",
cells: [
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,G,_,_,_,_,_,_,_,_,_,_,_,_,_],
// ... 9 行 × 38 列 の Gosper Gun を 直接配列で記述
],
},
// Glider / Pulsar / LWSS / R-Pentomino / Acorn / Diehard / ...
]
export function pasteCentered(g: Grid, pattern: Pattern): void {
const ox = Math.floor((g.w - pw) / 2)
const oy = Math.floor((g.h - ph) / 2)
clearGrid(g)
pastePattern(g, pattern, ox, oy)
}
各パターンは 0 / 1 の 2D 配列。 pasteCentered() で グリッドの中央に配置。 12 パターンを 1 ファイルに集約、 ~150 行で全網羅。 Gosper Gun (36×9) が最大、 Block (2×2) が最小。
8-近傍カウント (Moore neighborhood) + Wrap (torus)
function getCell(g: Grid, x: number, y: number, wrap = false): number {
if (wrap) {
const xx = ((x % g.w) + g.w) % g.w
const yy = ((y % g.h) + g.h) % g.h
return g.cells[yy * g.w + xx] ?? 0
}
if (x < 0 || x >= g.w || y < 0 || y >= g.h) return 0
return g.cells[y * g.w + x] ?? 0
}
function countNeighbors(g, x, y, wrap) {
let c = 0
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue
c += getCell(g, x + dx, y + dy, wrap)
}
}
return c
}
Wrap (torus 周期境界) で ((x % w) + w) % w の double-mod pattern で 負の剰余 安全化。 Wrap ON だと Glider が右端から消えても左端から再登場、 大型パターン (Gosper Gun) の Glider 放出が周期的に観察可能。
Canvas 描画 (緑バー + ink dots)
const cellW = CANVAS_W / g.w // 720 / 120 = 6px
const cellH = CANVAS_H / g.h // 480 / 80 = 6px
// bg = cream paper
ctx.fillStyle = "#f4ecd5"
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H)
// 緑バー (8 行ごと 4 行を緑) — 連続フォーム用紙 風
ctx.fillStyle = "#d6e8d0"
for (let y = 0; y < g.h; y += 8) {
ctx.fillRect(0, y * cellH, CANVAS_W, cellH * 4)
}
// alive cells (ink black)
ctx.fillStyle = "#1a1410"
for (let y = 0; y < g.h; y++) {
for (let x = 0; x < g.w; x++) {
if (g.cells[y * g.w + x] === 1) {
ctx.fillRect(x * cellW, y * cellH, cellW, cellH)
}
}
}
Canvas 720×480 を 60-240 倍率で描画。 image-rendering: pixelated CSS で アンチエイリアス無効、 純ピクセル表示。
RAF + speed throttle (1-60 fps)
function tick(now: number) {
const interval = 1000 / Math.max(1, speed)
if (running && now - lastTickRef.current >= interval) {
step(g, n, rule, wrap)
// swap, count, draw
lastTickRef.current = now
}
rafRef.current = requestAnimationFrame(tick)
}
RAF は常時 60fps で呼ばれるが、 interval 比較で 1 fps (slow motion) 〜 60 fps (high speed) を スロットリング。 React state (running / speed / rule / wrap) の依存配列で useEffect が再構成。
Pointer Events + Drag Draw + Toggle
const onPointerDown = (e) => {
canvas.setPointerCapture(e.pointerId)
const x = floor(((e.clientX - rect.left) / rect.width) * g.w)
const y = floor(((e.clientY - rect.top) / rect.height) * g.h)
const drawing = g.cells[y*g.w+x] === 1 ? 0 : 1 // toggle = clicked cell の逆 を以降の drag value に
drawingRef.current = drawing
setCell(g, x, y, drawing)
}
クリック位置 が 「死 → 生」 or 「生 → 死」 を判定して、 drag 全体で同じ値を塗る (= 描き始めの逆動作)。 setPointerCapture で canvas 外も継続。
やっていない / これからの IMPROVE
- Wireworld (4-state: empty / wire / electron head / electron tail) で回路シミュ
- Brian's Brain (3-state: alive / dying / dead) の振動子
- Langton's Ant (turmite) 1-cell walker
- Reaction-Diffusion (Gray-Scott) WebGL2 fragment shader 版
- 共有 URL で grid state + rule を base64 シリアライズ → SNS 再現
- PNG / GIF アニメ 出力 (canvas.toBlob / MediaRecorder)
- RLE (Run-Length Encoded) パターン インポート (LifeWiki 形式互換)
- WebGL2 GPGPU で 1000×1000 グリッド 60fps (現状 CPU 上限 240×160)
- 音響化 (alive count の変動を WebAudio で sonify)
- マルチプレイヤー (WebRTC で 2 人 同時描画 + 進化観察、 beam-drop 拡張)
次の SHIP は何 thesis に振るか
Thesis Audit:
cell-drift — シミュレーター (4本目)
glyph-forge — ジェネレーター (4本目)
soroban-rush — 学習 (4本目)
pop-drift — データ可視化 (3本目)
maya-cast — 占い (3本目)
直近 5本で 5 thesis 別 (連続なし)。 §3.1 完璧。 次の候補:
- 計算ツール 5本目 (育休給付金 / 退職金 / 国保 / 年金) — マネー計算機 5 本柱完成狙い
- 占い 4本目 (西洋占星術 / 易経 / 動物占い)
- データ可視化 4本目 (為替 30 年 / 都道府県 GDP / 気温 100 年)
- 8 thesis 残り 1 枠 = server-side AI (ユーザー許可待ち)
[ ./next_action ]
読んだら、 CellDrift を実際に動かす。
この開発ログは CellDrift をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。