A living world where every inhabitant is grown from a real memory β and every life they go on to live is written back as a new one. Propagate closes the loop on a self-hosted SuperMemory instance: memories in, a society out, lives back in.
Propagate is a fullscreen, isometric pixel-art farming simulation where every farmer is procedurally grown from a document in a self-hosted SuperMemory knowledge graph. Each gets a D&D-style character sheet, its own thoughts, relationships, and the drive to build a farm; together they raise a town that grows, remembers, and finds other towns. The whole thing renders through a CRT shader, with no build step β pure ES modules + Canvas 2D + a WebGL post-process.
βΆ Play the live demo β propagate.heyhaigh.ai
One click, nothing to install. (Drag to pan Β· click a farmer for their sheet Β· M for the world map Β· ROSTER to whisper to a farmer.)
Working on it? Start with AGENTS.md β it points at COMPATIBILITY.md (rules that keep live players' towns intact), TESTING.md (how to run the eleven test files) and HOSTING.md (how a change reaches production).
The substrate is a self-hosted SuperMemory instance (npx supermemory local,
http://localhost:6767) β local-first, never the cloud. Propagate uses it as a full
read β compile β write β re-read loop, not a one-time data dump:
- Grow from memory (read).
api/knowledge-graph.jslists the instance's/v3/documentswith a Bearer key that never reaches the browser; each doc is FNV-hashed into the seed that grows one deterministic farmer β archetype, stats, personality, and creed all fall out of that memory's content. - Memory is load-bearing (proof).
node tests/ablation.mjsfounds three towns from the same seed but different memory sources (real corpus / shuffled / invented) and shows they grow into observably different societies. Same seed, different memories β a different town: the integration changes the world, it doesn't decorate it. - Write lives back (write).
api/memory-writeback.jspersists each farmer's compiled inner life β and the town's political history β back into SuperMemory, tagged (ry-farmscontainer) so those generated life-docs are filtered OUT on read and never contaminate the source corpus. A farmer's remembered life outlives any save. - Walk the graph (portal). The memory portal (
memory-graph.html) reads the living graph back through/v4/searchand renders it in the sim's own CRT aesthetic β farmer hubs orbited by their creed, beliefs, and the source memories they grew from.
Compile-don't-query. The sim itself pulls its corpus ONCE at town founding and never calls SuperMemory again mid-tick β memory-derived content is frozen into a seeded, reproducible substrate. The whole read/write loop lives around the sim (founding, writeback, portal), never inside its hot path, so the world stays deterministic and testable.
Just play it β zero setup. Serve the folder and open it. No build, no keys, no accounts. The CRT world, farmers, crops, and buildings are all procedural, and it founds a town from an embedded offline crew, so it's fully playable out of the box:
node server.mjs # http://localhost:8000 (node server.mjs 8001 for another port)
# ...or any static server:
python3 -m http.server 8000Environment art (trees, rocks, wildlife, orc biome) comes from free CraftPix packs that their license doesn't allow redistributing, so they're not in this repo (
assets/is gitignored). The game degrades gracefully without them β the procedural farmers, crops, and buildings still render (that's what the live demo currently shows). For the full CraftPix look, download the free packs listed under Credits intoassets/.
Everything below is optional β each integration falls back cleanly if it's absent.
Point it at a self-hosted SuperMemory
instance in .env (copy .env.example). Without one, the town founds from the
embedded crew:
SUPERMEMORY_URL=http://localhost:6767 # npx supermemory local
SUPERMEMORY_API_KEY=sm_... # printed on the instance's first bootCompile-don't-query: the corpus is pulled ONCE at town founding; the sim never calls SuperMemory mid-tick β memory-derived content is frozen at founding.
The expressive channels run against any OpenAI-compatible endpoint β OpenAI by
default, or a local model for a fully self-contained stack. Set in the environment
or a gitignored .env:
OPENAI_API_KEY=sk-... # blank is fine for most local servers
OPENAI_BASE_URL=http://localhost:11434/v1 # e.g. Ollama β omit to use OpenAI
RY_FARMS_LLM_MODEL=gpt-4.1-mini # or a local model idLocal + free via Ollama (no key, nothing leaves your machine):
# 1. install Ollama β https://ollama.com (macOS: `brew install ollama`)
# 2. pull any chat model
ollama pull gpt-oss:20b # or llama3.2 / qwen2.5 β smaller = faster
# 3. Ollama serves an OpenAI-compatible API on :11434; in .env point Propagate at it:
# OPENAI_BASE_URL=http://localhost:11434/v1
# RY_FARMS_LLM_MODEL=gpt-oss:20bOr just set OPENAI_API_KEY and leave OPENAI_BASE_URL unset to use OpenAI. With
either configured, three out-of-band channels wake (all share api/_llm.js, so all
are endpoint-portable):
api/ry-farms-chat.jsβ farmers generate contextual two-line conversations from their goals, memories, relationships, weather, and town state.api/ry-farms-dm.jsβ the chronicler: a 5e-DM/fantasy-writer rewrites each farmer's procedural origin tale as richer prose, once per town, cached on the sheet and carried by the save (dm.jsis the client side).api/ry-farms-conscience.jsβ the conscience channel (see below): a two-stage call that CLASSIFIES a player's whisper into one bounded urge, then writes the farmer's in-character REACTION to the verdict the sim decided (conscience.jsis the client side). Offline it falls back to a keyword classifier + templates.
RY_FARMS_LLM_MODEL can override the model. Both channels β and the in-town
conversation LLM β are display-text only: they decorate the words on screen
but never draw sim RNG or write sim state, so the seeded sim is byte-identical
with or without them (the LLM decorates, it never decides).
Propagate is a living world first β towns that grow, remember, and (soon) find each other. Underneath that world is a reproducible substrate: a single town's founding and sim are deterministic β same seed β the same town, twice β so the whole thing stays testable and honest. That reproducibility is a substrate property, not the pitch: the world layer above it (expressive chat, encounters, cross-town migration, memories written back and re-entering as new lineage) is explicitly alive and non-identical β it's meant to diverge. Determinism is what we test; a living world is what we build.
The substrate is checked by a committed headless harness (sim only, LLM + SuperMemory off):
node tests/determinism.mjs
It boots the founder cast, ticks 30 days, and hashes farmer + world state across
two runs of each seed. same-twice=false is a P0 bug. The harness also pins
baseline digests; a legitimate sim change re-baselines them (update the constant),
but the same-twice property must always hold. (Cross-machine byte-identity is not
promised β the guarantee is per-town, LLM-off, headless reproducibility.)
Memory is load-bearing β proven by the ablation harness:
node tests/ablation.mjs
It founds three towns from the same seed but different memory sources β a real corpus, that corpus shuffled, and the invented fallback β ticks each 30 days, and shows they grow into observably different societies (different archetype mix, different creeds, different lived outcomes). Same seed, different memories β a different town: the SuperMemory integration changes the world, it doesn't decorate it.
A town is the reproducible substrate; the world is the living layer on top. Every town this browser grows is remembered in a world index and shown on a zoom-out map (the WORLD button, or press M): towns as memory-tinted dots, lines tracing which town each was founded from (the closed memory loop at world scale), and gold links where two towns have grown into each other's reach. Click a town to inspect it; VISIT switches to it.
Orc warbands. Add ?orc=1 to grow a town from the same SuperMemory substrate
through an inverted lens: the same memory that makes a Designer makes a Runecarver;
cultivate-creeds become raid-creeds; personalities weight to competitiveness and temper.
On the world map an orc warband is ashen and blood-red, and when it grows into a human
town's reach the encounter is a raid, not a trade β and the raided town remembers.
Every farmer is deterministic: the same memory always grows the same farmer (until you mutate it).
- A SuperMemory document is hashed (FNV-1a) into a seed for a
mulberry32RNG. - Keyword clustering sorts the memory into an archetype β Voice Herald, Athlete, Designer, Builder, Homebody, or Greeter β which sets crop, hat, and palette.
- Six ability scores (STR / DEX / CON / INT / WIS / CHA) are rolled 4d6-drop-lowest, then nudged by keywords in the memory text and the archetype's bias.
Those stats drive real behavior:
| Stat | Effect in the sim |
|---|---|
| STR | Water-carrying capacity (4 vs 2 pails) and build speed |
| DEX | Walk and work speed |
| CON | Keeps working through storms; crops roll the owner's CON to survive lightning (DC 12); resists illness |
| INT | "Green thumb" growth bonus + the harvest d20 (natural 20 = Γ3 crit yield) |
| WIS | Harvests ripe crops before a storm hits; waters early in a drought |
| CHA | 14+ radiates a morale aura: +15% work speed to farmers within 6 tiles |
Farmers earn XP from work and level up, gaining +1 to a random stat.
Where stats decide how well a farmer works, four personality axes (also read from the memory) decide how they behave. Each is a 0β1 value, and their blend produces a named identity and a one-line creed (Pillar, Cutthroat, Schemer, Lone Wolf, Workaholic, Free Spirit, Straight Shooter, and more):
- Teamwork (collaboration) β high: readily answers the help board, joins town projects, checks on sick neighbors. Low: keeps to their own fence line.
- Drive (competitiveness) β high: chases the top of the harvest leaderboard, works later when behind, won't help their direct rival.
- Honesty β high: only asks for help when truly needed, always reciprocates, calls out thieves. Low (manipulative): posts fake help requests to farm free labor, and will quietly poach a ripe crop from a neighbor when nobody's watching β losing reputation if an honest neighbor catches them.
- Work ethic (diligence) β sets how late they'll stay up (see below).
Every farmer also carries a reputation that helping raises and cheating lowers; low-reputation farmers get help less readily.
Work drains energy; sleeping in one's own house restores it. Crucially, each farmer picks their own bedtime from their work ethic and competitiveness β a Free Spirit turns in the moment night falls, while a Workaholic (or someone trailing the leaderboard) burns the midnight oil, working by lantern-light long after the others are asleep. Tired farmers move and work slower and fumble harvests more.
But there's a cost to overwork: chronic late nights build sleep debt, and each dawn an exhausted farmer must make a CON save or fall ill. A sick farmer stays home in bed, works at a crawl, and recovers over a few days β faster if a kind-hearted neighbor brings them soup.
The year loops Spring β Summer β Fall β Winter (a few days each). Seasons retint the ground, shift the weather odds (summer brings droughts, winter brings storms and snow), and change how fast crops grow β winter nearly freezes growth, while summer races it. Watch for drifting snow and falling autumn leaves.
Farmers aren't on fixed scripts β each runs a small decision loop every tick:
- Thoughts. A farmer narrates what it's doing (and, when idle, muses β sometimes quoting the memory it was grown from). Shown on the character sheet and surfaced as speech bubbles.
- A help economy. A farmer swamped in a task it's bad at posts to the town help board. Idle neighbors who are good at that task walk over, work the requester's fields (the harvest credits the owner), and the two form a bond.
- Communal projects. As the town's total harvest grows, projects unlock one at a time β Toolshed, Windmill, Storm Tower, Second Well β each granting a town-wide buff. Farmers converge on the scaffold and build it together.
- Growth. Farms expand their fence line at harvest milestones, and when the first ring of homesteads fills up, a new ring opens farther out and the town grows.
In the bottom half of the ROSTER window you can whisper a thought to any farmer. You are not a character they can see; you're a stray inner voice. What happens next is decided by the sim, not the words:
- The whisper is classified into one bounded urge (chop, plant, explore, build, rest, hunt, trade, visit someone, or nothing).
- A deterministic check β seeded by the farmer, the urge, and the day, never by the message β returns one of six verdicts: HEED (takes it up as their own idea), ALREADY (they were going to anyway), BARGAIN (later, once the work's in), DISMISS, QUESTION (wonders where the thought came from), or DEFY (bristles and does the opposite).
- The farmer replies in character, coloured by a fixed stance toward the voice (skeptic / believer / bargainer / unbothered).
Because the roll is seeded by the kind of thing you ask and the day β not the sentence β asking fifty times won't change a mind. A heeded urge only ever nudges a decision (capped below the pull of their lifelong dream), expires by the next day, and is bounded by a daily and town-wide budget so the voice can never turn the town into a puppet show. Whispers stay private: they never leak into gossip, the chronicle, or farmer-to-farmer chat. The whole conversation is saved with the town.
- Drag to pan the camera.
- Click a farmer to open their character sheet (personality traits, energy & health, stats, current thought, bonds, reputation, farm size, and the memory they were grown from).
- ROSTER (top-right) opens a scrollable town-wide list β every Ry's level and stats at a glance, sorted by yield. Click a row to jump to their sheet. The bottom half is the CONSCIENCE CHAT: whisper a thought to any farmer (pick who with the name dropdown) and they'll act on it, ignore it, or wonder where it came from β never simply obey. See below.
- +RY (top-right) grows a new farmer from an unused memory.
| File | Role |
|---|---|
dna.js |
Fetches memories, hashes seeds, classifies archetypes, rolls stats, builds farmer sheets |
farm.js |
The simulation: world grid, crops, weather, day/night, the farmer agent loop, help board, communal builds, expansion |
pixel.js |
All procedural pixel art β farmers, crops, buildings, props, and a 3Γ5 bitmap font |
crt.js |
WebGL CRT post-process (scanlines, RGB mask, chromatic aberration, corner vignette, flicker) |
main.js |
Rendering, camera, input, HUD, boot, and the roster/conscience-chat UI |
conscience.js |
Client for the conscience channel β classify, sim check, reply, offline fallbacks |
api/knowledge-graph.js |
SuperMemory read β server-side proxy that lists /v3/documents (Bearer key never reaches the browser) and normalizes the corpus that grows the town |
api/memory-writeback.js |
SuperMemory write β persists each farmer's compiled life + the town's history back to SuperMemory, tagged so it never re-enters as a source memory |
api/memory-graph.js + memory-graph.html |
The memory portal β reads the living graph via /v4/search and renders it in the sim's CRT aesthetic |
tests/ablation.mjs |
Proof memory is load-bearing β same seed, different memory sources β observably different societies |
v1-3d/ |
A preserved first experiment: Three.js SDF smooth-min creatures with seamless blended bodies |
Built by Ryan Haigh with Claude Code as an expression of his "virtual self" β worker farmers drawn from his own stored memories. Intended to live on heyhaigh.ai.
- Memory β SuperMemory, self-hosted; the genome every farmer is grown from.
- Local LLM β Ollama powers the offline expressive channels (chat / chronicler / conscience).
- Environment art β free top-down pixel-art packs from CraftPix, used under their free license and not redistributed here (download them into
assets/for the full look). Packs used: top-down trees, bushes, rocks & stones, forest objects, rocky-area objects, hunt-animals, farm-animals, plants-for-farm, guild-hall, the "main characters home" set, the orc character pack, the swordsman (lvl 1β3) set, and the basic RPG UI + 16Γ16 fantasy-icon sets. - The procedural farmers, crops, buildings, CRT shader, and bitmap font are original (
pixel.js/crt.js); character and tile variety takes cues from Brandon James Greer's pixel-art breakdowns.