Skip to content
KunskapsHungerPublic

About

A peer-to-peer elemental arena in a living grove that remembers everything. Fire, water, wind, weather and creatures collide — no scripts, just systems.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

EMBERWILD

A year in a grove that remembers everything.

▶ Play now: https://kunskapshunger.github.io/emberwild/ — host a grove and send friends the invite link.

Emberwild is a peer-to-peer elemental arena for 1–6 wardens. You fight over one living grove through four seasons. Nothing in it is scripted. Each spell changes the world, and the world's systems decide what happens next: fire, water, wind, gas, lightning, weather, ecology and creatures. The map carries over between seasons, and the Grove, which narrates the match in its own voice, reacts to what you did to it.

Play

npm install
npm run dev          # → http://localhost:5173
  • Play solo starts a match against AI wardens ("echoes"). They use the same combos you can.
  • Host a grove gives you a 5-letter code and an invite link. Friends connect straight to your browser.
  • Join takes a code. Other tabs in the same browser connect instantly, and everyone else connects over WebRTC. Add ?net=online to force WebRTC when testing on one machine.
Input Action
WASD / arrows move
mouse aim
left click (hold) cast selected spell
right click quick Gale
1–6, Q/E, wheel choose spell
Space dash
Tab scoreboard
M mute

The systems

System What it does Emerges as…
Fire Spreads by fuel, moisture², wind direction and heat. Pre-dries fuel ahead of the front. Wind throws embers downwind. Summer wildfires, fire walls blown toward enemies, spot fires across the stream
Moisture Every cell drifts toward the season's climate. Water and mud keep neighbours damp. Rain soaks, drought parches. The same meadow is safe in spring and a tinderbox in summer
Gases Smoke, steam and bog gas move with the wind and diffuse. Bog gas seeps from mushrooms and autumn mud and pools in hollows. Chain explosions rippling through a swamp when one ember lands
Electricity Floods through connected water, mud and rune floors, and through wet bodies. Tide + Spark combos, storms turning ponds lethal, self-electrocution
Ice & snow Frost freezes water into slippery bridges. Winter nights freeze ponds from the edges. Fire melts ice into puddles. Skating escapes, bridges that melt under a pursuer
Ecology Ash regrows into grass. Grass flowers, brush thickens and trees spread. Mossbacks graze, and moths pollinate. Burnt zones heal into fresh fuel. The map evolves across the year
Creatures Mossbacks herd, graze and stampede from fire, spreading it. Glowmoths swarm light and catch fire. Bog slimes take on the element they touch. Hollow wolves hunt at night and fear fire. A burning sheep running into your enemy's forest
Weather Seasonal fronts: rain, storms (dry lightning in summer), drought, gales and snow. Day/night cycle. The battlefield changes under you every ~30 s
The Grove's mood Destruction vs. regrowth each season. A wrathful grove brings storms, wolves and slimes. A blooming one brings wisps, mana and growth. Players who burn everything make the next season harder for everyone
Attribution Every burning cell, charged puddle and gas blast remembers who started it. "Mira's wildfire consumed Tomas", credited to Mira

Scoring: kill +3, wisp +1 (wisps rise from burnt trees and the fallen), undone by your own magic −1.

Architecture

index.html ── src/main.js ─┬─ net/session.js   HostSession (Web Worker + PeerJS + BroadcastChannel) / join
                           ├─ client/          GameClient (prediction, interpolation), Renderer, tiles, lighting, particles, sprites
                           ├─ audio/audio.js   procedural SFX, adaptive generative music, narrator voice bus
                           └─ ui/              HUD, menus (DOM, textContent only)
server/worker.js ── server/server.js  authoritative GameServer (20 Hz)
                      ├─ sim.js        cellular automaton: fire, gases, charge, moisture, ecology
                      ├─ climate.js    seasons, weather, wind, day/night, Grove mood
                      ├─ spells.js     6 spells + projectiles + storm strikes
                      ├─ creatures.js  mossbacks, moths, slimes, wolves
                      ├─ combat.js     status effects, damage, kill attribution
                      ├─ bots.js       AI wardens
                      └─ mapgen.js     procedural grove
shared/  constants, codec (16-bit cell encoding, run-length grid diffs, binary framing), physics, rng
  • Topology. The network is a star over WebRTC. The host's browser runs the authoritative server in a Web Worker, so the grove keeps simulating when the host's tab is in the background. PeerJS's public broker is used only for the handshake; game traffic goes peer-to-peer.
  • Replication. The 192×112 grid is sent as 16-bit visual cells: material, fire, moisture, gas, charge. Each tick sends a run-length diff, and new joiners get a full frame. Entities are sent as compact JSON snapshots and interpolated about 110 ms behind.
  • Movement. Your own warden is predicted client-side against the replicated grid. The host validates the positions it receives and pushes impulses and teleports back.

There is no bundler. The game is native ES modules plus a vendored peerjs.min.js, and there are no native npm dependencies. The dev server, build script and tests are plain Node.

Build & deploy

npm run build        # → dist/ (static; host anywhere: GitHub Pages, Netlify, Vercel, itch.io)
npm run preview      # serve dist/ locally

Tests

npm test             # node:test, 39 tests including a full headless 4-season match with 6 bots
npm run test:coverage

Narrator voice

The Grove's 34 lines were generated with Google's Gemini 3.8 TTS (gemini-3.8-flash-lite-tts, voice Algenib) and live in public/voice/. Subtitles work without audio. To regenerate them:

GEMINI_API_KEY=... npm run voice                 # only missing lines
GEMINI_API_KEY=... npm run voice -- --force      # all lines
TTS_MODEL=gemini-3.8-flash-tts GEMINI_API_KEY=... npm run voice -- --force   # flagship model

The key is read from the environment only and is never shipped. Free-tier quotas are about 10 requests/min and 100/day per model; the script throttles itself and stops cleanly when the daily quota runs out.

About

A peer-to-peer elemental arena in a living grove that remembers everything. Fire, water, wind, weather and creatures collide — no scripts, just systems.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages