
어릴 때 하던 모래 게임이 그리워서 직접 만들었습니다 (샌드폴 랩)
물리 엔진 없이 셀 8만 개를 매 프레임 굴리는 falling-sand 시뮬레이터. 60fps 사수했습니다.
자랑 포인트
- 외부 물리 엔진 없이 셀룰러 오토마타를 직접 구현 — 8만 셀을 매 프레임 갱신하면서도 60fps 유지
- Uint8Array·Uint16Array로 셀 상태를 표현해 객체 생성 0, ImageData 직접 쓰기 + putImageData 한 번으로 렌더링
- 물+불=증기, 용암+물=돌, 화약 연쇄 폭발까지 — 12원소가 프레임 단위로 상호작용하는 살아있는 픽셀 생태계
어릴 때 falling sand 게임 해보신 분 계신가요. 모래 뿌리고 물 붓고 불 지르면서 시간 순삭되던 그 플래시 게임이요. 문득 그게 그리워서 찾아봤는데 플래시가 죽으면서 같이 사라졌더라고요. 그래서 만들었습니다. 브라우저에서 돌아가는 픽셀 물리 시뮬레이터, 샌드폴 랩입니다.

모래를 뿌리면 쏟아져 쌓이고, 물은 바닥을 찾아 수평이 되고, 불은 나무를 타고 번집니다. 원소는 12가지인데 조합이 포인트예요. 물과 불이 만나면 증기가 피어오르고, 용암이 물에 닿으면 돌로 굳고, 씨앗에 물이 닿으면 초록 줄기가 위로 자랍니다. 화약에 불붙이는 게 제일 재밌는데, 폭발이 셀을 타고 도미노처럼 번지는 걸 보고 있으면 시간이 사라집니다. 어릴 때랑 똑같이요.
데모 씬 4종(기본 풍경/폭발 실험/용암 폭포/비 내리기)이 있어서 빈 화면에서 막막할 일은 없고, 1~30 크기 브러시로 그리고 우클릭으로 지우고, 일시정지·한 스텝 진행·0.5x/2x 배속까지 됩니다. CRT 스캔라인 토글은 그냥 제 감성입니다.

이제 기술 얘기를 해야 하는데, 이 프로젝트의 진짜 도전은 여기 있습니다. 384×216 그리드, 셀 8만 개를 매 프레임 갱신하면서 60fps를 지키는 것. 외부 물리 엔진 없이 셀룰러 오토마타 규칙만으로요.
답은 "자바스크립트 객체를 안 만드는 것"이었습니다. 셀 상태는 Uint8Array(원소 id)랑 Uint16Array(수명/에너지) 두 장으로만 표현합니다. 셀마다 객체를 만들면 GC가 프레임을 잡아먹거든요. 렌더링도 ImageData 버퍼에 픽셀을 직접 쓰고 putImageData 한 번으로 끝냅니다. DOM 조작도, 수만 번의 draw 호출도 없이요. 난수도 Math.random 대신 xorshift32를 인라인으로 돌리는데, 프레임당 수만 번 불리는 함수라 이 차이가 생각보다 컸습니다.
디테일 하나 더. 셀을 항상 같은 방향으로 훑으면 모래가 한쪽으로 쏠립니다. 그래서 열 스캔 방향을 프레임마다 좌우 번갈아 바꾸고, 프레임 스탬프로 같은 셀이 한 프레임에 두 번 움직이는 것도 막았습니다. 이런 건 만들어보기 전엔 상상도 못 한 문제였어요.
React 19 + TypeScript + Vite + Tailwind 조합이고, UI(팔레트·컨트롤·HUD)는 React가, 시뮬레이션 루프는 React 바깥의 순수 타입드 배열 로직이 맡습니다.
물리 엔진 쓰면 훨씬 편했겠지만, 셀 하나하나가 왜 그렇게 움직이는지 직접 규칙으로 써 내려가는 게 이 프로젝트의 재미였습니다. 설치 없이 브라우저에서 바로 됩니다. 들어가서 일단 화약에 불부터 붙여보세요. 다들 그것부터 하더라고요.