How Effectstream thinks
One mental model carries you through this whole workshop: blockchains are input devices; your game state lives in PostgreSQL. Three chains feed our game — an EVM chain holding the NFTs, another EVM contract (EffectstreamL2) where batched player moves land, and Midnight for the seasons. Effectstream itself is the L2: the deterministic state machine that consumes all three.
(in this workshop's dev setup, both EVM roles run on a single local Hardhat chain; in production they can be separate networks)
Six words you'll hear all session
| Term | What it is | Where it lives |
|---|---|---|
| network | a chain (or clock) the node connects to | config.dev.ts |
| sync protocol | how blocks are fetched from one network; one main timeline, many parallel chains | config.dev.ts |
| primitive | a watcher on one contract that turns chain events into typed inputs | config.dev.ts |
| grammar | the schema of every input your app understands | grammar.ts |
| state machine (STM) | your logic: one transition function per input kind, writing to Postgres | state-machine.ts |
| batcher | collects signed user inputs off-chain and lands them on-chain in batches — users pay no gas | packages/batcher/ |
Why determinism is the whole point
Anyone can run this node. It reads the same chains, applies the same state transitions, and arrives at byte-identical Postgres state. That's what makes the game state trustless: the chain is the source of truth, and the node is just a deterministic replay of it.
It's also the one rule you must never break inside a state transition:
no Math.random(), no Date.now(), no network calls.
(In Step 2 you'll see the framework hand you a
seeded random generator instead — that's how our cards get random stats
without breaking determinism.)