Agar.io-style multiplayer game built on MODU Engine.
npm install
npm run dev # Starts dev server on http://localhost:8080
npm run build # Production build
npm test # Run determinism & rollback testsGames can live in their own repos while staying deterministic through 3 mechanisms:
The build.js esbuild plugin automatically converts non-deterministic math to deterministic equivalents:
// You write:
Math.sqrt(x)
Math.random()
// Build transforms to:
dSqrt(x) // Deterministic square root
dRandom() // Seeded PRNGThis happens transparently - write normal code and the build handles determinism.
Enable the debug overlay to catch sync issues early:
import { enableDebugUI } from 'modu-engine';
enableDebugUI(game);Shows:
- Frame number & hash (compare across clients)
- Entity counts
- Network stats
- Snapshot sync status
If hashes diverge between clients, you have a determinism bug.
For cases the auto-transform can't handle, use explicit deterministic functions:
import { dRandom, dSqrt, fpMul, fpDiv } from 'modu-engine';
// Random (seeded, reproducible)
const value = dRandom(); // 0-1
const index = (dRandom() * 10) | 0; // 0-9 integer
// Math
const dist = dSqrt(dx*dx + dy*dy);
// Fixed-point (for precise physics)
const result = fpMul(a, b); // a * b in fixed-point
const ratio = fpDiv(a, b); // a / b in fixed-pointRun the test suite to verify your game logic is deterministic:
npm test # All tests
npm run test:determinism # Just determinism tests
npm run test:rollback # Just rollback testsTests spawn multiple simulated clients and verify they produce identical state hashes.
- Mouse: Move towards cursor
- Space: Split
src/game.ts # Game logic (systems, entities, input)
build.js # esbuild config with deterministic transforms
dist/ # Built output (game.js + index.html)
tests/ # Determinism & rollback tests
The game imports from modu-engine which is aliased to the local engine source during build, ensuring you always use the latest engine.