A cozy, simulation-driven garden game in a single HTML page. Three.js renders a floating storybook island; a real ecological simulation drives everything you see — moisture moves through soil layers, temperature and daylight gate growth, weeds compete, pollinators help, and seasons rewrite the world. New plants and decor are data, not code: genomes that the procedural builders interpret, which is what lets crossbreeding and AI discovery mint brand-new content at runtime.
Built on @fundamental-engine/three — the reciprocal
field engine drives the pollen swarm, breeds hybrids, and steers critters.
Play: open index.html or run python3 -m http.server 3000.
Architecture
index.html page shell: CSS theme, script load order
app/
core.js shared utilities (RNG, neighbor offsets, toast/float events)
species.js ── DATA ── genome registries + sanitizers + crossbreed/lineage + AI generation
world.js ── MODEL ── game state, simulation tick, ecology, economy, save/load
engine.js ── VIEW ── renderer, camera/controls, sky, day/night lighting
plants.js genome plant mesh builders + shared procedural textures
tiles.js tile meshes (INSTANCED: soil horizons, slabs, grass), ground/water shaders, decor, pipes, picking & tools
field.js POLLEN FIELD + FieldKit — the @fundamental-engine force field as a game engine (see docs/FieldKit.md)
fundamental-engine-three.js vendored @fundamental-engine/three@0.7.0 particle+force engine
effects.js particle systems: action bursts, rain, snow
critters.js the whole live ecology: bees/butterflies/fish + bunnies, fox, owl, hawk, heron, frogs, gnats, fireflies
thumbs.js offscreen render-to-image of a species mesh for Dex cards (window.SpeciesThumb)
audio.js synthesized SFX + 3 bundled .sf2 instruments + day/night/weather ambience
ui.jsx ── HUD ── React: status bar, toolbar, inspector, panels, Tweaks
store.jsx General Store panel + plumbing overlay
market.jsx Seed Market panel (buy, crossbreed, AI discovery)
dex.jsx The Dex panel: catalogue + genome charts + family tree + breeding bench
tweaks-panel.jsx Tweaks shell (starter component)
*.sf2 bundled piano/guitar/violin soundfonts (parsed in-browser, no CDN)
docs/ deep-dive design docs (read these — they are the spec)
How-It-Works.md the conceptual spine — read this first
Methodology.md how to couple systems to the field & build reactive behavior (process)
Backlog.md design risks, open questions & fixes (counterweight to How-It-Works)
FieldUI-Guide.md extensive guide to the fundamental-engine (setup, forces, sensing, perf)
FieldUI-Opportunities.md roadmap for leaning harder on fundamental-engine, mapped to the backlog
FieldUI-Engine-Features.md engine capability spec (project-agnostic — for the engine lib itself)
FieldKit.md the field-as-game-engine API + engine gaps/wishlist
Ecology.md thrive/die model, animals, food web, night chorus
Genetics.md genome schema, inheritance rules, pollination-driven hybridization
Appearance-Model.md how a genome renders across lifecycle/season/weather/field (the body-vs-organ model)
Migration.md the moving-day manifest — state, debt, tuning risks, fundamental-engine upgrade map, cleanup checklist
Dex-and-TileInfo.md Dex + tile-inspector UI spec
Design-Guidelines.md UI design system (cream/amber storybook HUD) + layout/craft rules
Shaders-and-Lighting.md the painterly look — warm/cool grade, backlit grass & foliage, INSTANCED tiles
Visual-Vocabulary.md the visual language (world + HUD) — the why/feel behind the systemLoad order (matters!)
three.js core.js species.js world.js audio.js plants.js engine.js tiles.js effects.js critters.js thumbs.js fundamental-engine-three.js field.js Engine.start() React (tweaks-panel/market/store/dex/ui).
species.js must precede world.js (the world references live genome registries).
world.js must precede engine.js (the frame loop ticks the world).
field.js loads after the engine/critters (it reads window.Engine/World/FieldUI).
Within a module, declaration order matters too. Modules are one big IIFE; a
const mat = makeShader(...)that reads aconst uniformsdefined lower in the file is a temporal-dead-zone crash that takes out the whole module. Define shared uniforms before any material that closes over them.
Data flow
user input ──► tiles.js (picking) ──► World mutation ──► touch(x,z) ──► tile rebuild
│
frame loop ──► World.tick(dt) ── simulation ──┴──► notify() ──► React HUD re-render
└────► debounced saveWorld.touch(x, z)— a tile changed; the renderer rebuilds that tile's meshes. (Instanced surfaces — soil horizons, slabs — re-record into their shared buffers and rebuild once per frame; only per-tile contents live in a disposable group.)World.notify()— HUD-level state changed (points, weather, inventory…).- Both schedule a debounced save (2.5 s); saves also run every 15 s, on tab hide,
pagehide, and before unload. Save key:
verdant-garden-save-v2in localStorage.
The ecology (the heart of the game)
Life runs on one reusable rule — suitability vigor (see docs/Ecology.md and
docs/Genetics.md for the full spec):
World.suitability(tile, def)/grassSuitability/weedSuitabilityscore how well conditions suit an organism (temp × moisture × soil quality). A per-occupantvigordrifts towardsuitability − rivalSuitability; high vigor spreads, low vigor dies. Clover vs grass, weeds vs crops, and dirt-greening all use this.- Nutrient loop: growing crops draw down soil quality; harvests deplete; compost,
leaf-fall, mushrooms (
t.fungus) and animal kills (World.enrich) replenish it. - Food web (critters.js): bunnies forage grown veggies (skipping fenced beds)
fox/owl/hawk hunt bunnies (day & night), heron hunts frogs/fish. Each predator emits
a
FieldKitpredator body so prey flee via the field. Populations are vigor-driven, not capped. Activity patterns (diurnal/nocturnal/crepuscular) gate who's awake. - The field is the attractor: blooms/ripe/groves are wells, sprouts draw gently;
pollen density (
PollenField.densityAt) drives growth speed, hybridization, music, and critter steering. Crossbreeding happens naturally where different blooms meet in dense pollen —WorldcallsSpecies.crossbreedand stamps lineage. - Night chorus: a wet spell beside a pond brings frogs + gnats + brighter fireflies.
The simulation (world.js tick)
Per in-game hour, per tile:
- Moisture in three layers / soil horizons — surface=topsoil (rain/watering in,
evaporation out), shallow=subsoil (percolates down, roots drink, diffuses to neighbors,
pond seepage), deep=substratum (settles toward the water table; capillary rise feeds
shallow). All per-hour rates live in the
SOILtable at the top ofworld.js(perc1/perc2,capThresh/capRate,gwSettle,seep,plantDraw,qRetention). The soil column renders these layers as tinted horizons (seedocs/Ecology.md). - Temperature = season base + diurnal swing + weather. Each species has a comfort range: cold stalls or frost-wilts tender plants; heat stresses unless watered.
- Lifecycles — annuals age & die (can self-seed), biennials bolt, perennials go dormant in winter. Growth needs daylight; clouds slow it.
- Weeds — bare dirt invites them; mature weeds invade neighbors and planted beds (choking growth). They only run in spring/summer. Pull them with the hand/gather tool.
- Irrigation — pipes BFS from ponds (
computeFed); sprinklers need supply and a schedule window (dawn/dusk/both), freeze all winter, and can waterlog beds (root rot). - Economy — harvests pay (× clump count); seed drops back to your pouch; dawn stipend per living plant; honey from hives near blooms; expansion costs .
Genomes (species.js)
Plant genome: { id, name, emoji, cost, harvest, cycle, stages, growHours, temp, form: { type: stalk|rosette|branching|vine, … }, ecology: { bloom, shade, selfSeed, seedDrop, spread } }. The four form types are interpreted by plants.js; ecology
traits plug into the simulation automatically — any new genome fully participates.
Decor genome: { id, name, desc, cost, seasons?, form: { parts: [up to 12 primitive parts], glow, attract } }. Parts (box|sphere|cylinder|cone|ico|torus + size/pos/color/
water/glow) are assembled by tiles.js, which also renders each custom item's 3D
catalog thumbnail (window.DecorThumb).
New genomes come from: crossbreeding (local mutation blend, 5), AI discovery (10, once/day), and General-Store backorders (8, once/day, delivered at dawn). All AI output is strictly sanitized — every field clamped/whitelisted — with a procedural fallback so a failed call never wastes the spend.
Controls
- Orbit right-drag (or drag on empty sky) · pan WASD/arrows, drag, middle-drag
- rotate Q/E · zoom scroll, +/− · tools number keys or toolbar
- Drag across tiles to paint with most tools. Right side panels: Store, Market, Weather.
- Photo mode hides the HUD and slowly orbits. Esc exits.
Development
Conventions
- Conventional commits.
feat(scope): …,fix(scope): …,docs,chore,refactor. - Modules are IIFEs exposing one
window.Xglobal; no build step, no imports. - All randomness that must be stable across rebuilds is seeded (
Core.rng(seed)). - Materials are cached/shared wherever tiles repeat them (see
tiles.jsTILE_MATS). - Repeated tile geometry is instanced — soil horizons (3
InstancedMesh, moisture- tinted viainstanceColor), top slabs (oneInstancedMeshper tile-type, custom top shaders gated on#ifdef USE_INSTANCING), and grass blades (one mesh, all blades). - UI strings live in the component files; simulation tunables sit at the top of
world.js(SEASON_*,SOIL,POLLEN, costs) andspecies.js(genome clamps).
Verification
Load index.html in a browser, check the console for errors. The field should render
a pollen swarm around blooming plants; critters should move; the soil horizons should
darken with moisture.
Built with
- Three.js r184
- Fundamental Engine —
@fundamental-engine/three@0.7.0(GitHub) - React 18 (UMD, in-browser Babel for JSX)
License
MIT © Zach Shallbetter