MEME
WARS.

A stick-figure meme co-op shooter, engineered like an operating system — content is data, the engine never learns what a weapon is.

Node.js + HTML5 Canvas · host-your-own-server · no build step · MIT

01 — The Thesis

Build the perimeter. Fill the middle with data.

The whole project rests on one architectural bet: write generic mechanisms once, then let content pour in as plain JSON. The Unix idea — build search, and users / devices are just data it walks over.

Adding "Shoop Da Whoop" is a line in data/weapons.json, not new engine code. The netcode has no idea what a weapon is — it syncs anonymous components. The same JSON is read by the server, every client, and the tools; there is no build step and no second copy to drift. npm run check refuses to start if any weapon, wave, or AI names a blueprint that doesn't exist.

The crown jewel — one line generates every game mode
Two entities are hostile iff their teams differ. Co-op shares a team so shots pass through friends; free-for-all gives everyone a team of one; Red-vs-Blue shares a side. A shot carries its shooter's team and hits whatever isn't it — which is why no blueprint ever names a target.

Composition over inheritance

No class hierarchy. An entity is the set of components it carries; a data blueprint describes that set.

Decorators at spawn

homing, rainbow, piercing are decorators the registry composes onto a base shot — never bespoke projectile code.

One simulation, both sides

The same game.js runs authoritatively on the server and can predict on the client. That reuse is the payoff of a content-agnostic engine.

02 — Architecture

Two orthogonal machines on one shared foundation.

There are two dependency-injection systems and they deliberately don't overlap: one runs the process (sockets, clock, render), one runs the game (what a weapon does). Keeping the sim out of the service layer is why features ship fast without destabilising the plumbing.

📦 data/ — the source of truth
Plain JSON, read identically by server, client and tools. Tools write here; nothing else does.
content
weapons.jsonblueprints.jsonwaves.jsonpickups.jsonmodes.jsonworld.jsontiles.jsonfigure.jsonanimations.jsonaudio.jsoncontrols.jsonrooms.json
↓ loaded as JSON modules — no build, no copy ↓
⚙️ The Game Machine — ECS registry + PIPELINE
Blueprints, behaviors & decorators composed into entities; generic systems that know only components, never each other. Communicate through an event bus.
the sim
engine · the perimeter
registryworld ·ECSchunksterrainnoisetilesdamagephysicsmathreachlua-host
systems · generic, component-only
controlaimotioncombatdirectorjetpackpickupsphysicsdoorsmatchctfkothcp
content · loads data, adds behaviours
indexfigureanimationsmodesfeaturesplugins/*.lua
🔗 src/glue/ — the isomorphic foundation
Kernel + LanguageTools + EmitterFeature import only themselves — no node:, no DOM — so the same code boots both Kernels.
shared
KernelLanguageToolsConfigServiceEmitterFeature ·event bus
🖥️ Server Kernel
Authoritative · 30 Hz tick · many worlds, one process.
host
indexNetServiceWebServiceWorldsServiceClockServicechat
🌐 Client Kernel
One per tab · render interpolated snapshots · send input.
browser
mainRenderServicerendererNetServiceinputaudioconsole

ember nodes are the codebase's hottest files by commit count · cyan nodes are the new Lua plugin substrate. Wire protocol between the two Kernels is unchanged by all of this — it was internal reorganisation (the SOA migration), behaviour-preserving by design.

03 — The World

An infinite world nobody ships and nobody stores.

Terrain is a pure function of the seed — server and every client build identical ground with nothing streamed. A spot a mile away is in the same place for everyone the moment someone walks to it. Only a pickup site's state (taken, due-back) is the server's, because that's the part players disagree about.

layer 1 Base

Anisotropic Perlin noise, correlated further sideways than up — so solids form long horizontal platforms with frequent gaps.

layer 2 Features

Rare per-chunk overrides: a void gap in the sky, or a fortress lattice of rooms.

layer 3 Structures

Rare stamps of fixed shape with guaranteed loot — a walled sanctuary holding armour, health, two good weapons.

Generators are data

original · one ground · koth mound · cp arena

Per-world Generator instances owning their own caches, chosen by name. "One Ground" grows dirt-and-grass hills with tunnels and sparse floating platforms above.

Floor modes are data

lava line · none · water (planned)

The old hardcoded "below the red line you take damage" generalised into a data-selected floor behaviour — the same "world-rules are data" pattern as generators.

Tiles are no longer just solid/empty — each carries a stack of objects (a wood base + a door modifier = a wooden door), and takes damage on a 6×6 sub-grid, so a shot chips the shape of a block and future collisions follow the chips. Greedy-meshed colliders let a connected blob read — and collide — as one platform.

04 — The Arsenal

Twelve memes. Zero of them are engine code.

Every weapon below is a JSON entry choosing a generic behaviour and wiring a projectile blueprint. behaviour is the mechanism the engine provides; the rest is data.

05 — Modes & Teams

Seven modes, one hostility rule.

Modes are data too — a label, a team list, a scoring rule, some limits. The engine reads teams and the hostility rule does the rest. Most modes pick a world generator; some flip a single behaviour flag (ctf, koth, cp).

06 — The History

Eleven days. A hockey-stick of commits.

A slow, careful start — foundations and the tile world — then two days that detonated: 33 commits, then 84. That pace only holds because every feature ships with a headless sim and check gates the references.

▮ commits per day net churn since day one · +27.5k / −9.5k lines
Jul 11–18foundations
The world & the engine. ECS registry, the tile grid, procedural terrain, the stick figure that won't save if its knees bend backwards. The perimeter goes up first.
Jul 19Sets 1–2 · 33×
It becomes a game. Chat & /to routing, spawn structures, nuanced firing (bursts, precision, charge), random names in LocalStorage. Then the big one — the SOA / Kernel migration lands on both server and client.
Jul 20Sets 3–11 · 84×
Content flood + three re-platformings. Jetpacks, music with ping-corrected song-sync, fish & dodgeball, block build/mine, subtile destruction — while the floor is rebuilt underneath them: post-SOA cleanup, then matter.js physics, then modes CTF · Adventure · KotH · Control Points. Ends by choosing Lua for plugins.
Jul 21Set 11 · in progress
The Lua host goes in. Fengari sandbox VM, the engine↔Lua contract, plugin↔plugin dependencies, defineWeapon through a host sink. "This Is Fine" is reborn as an authored plugin. This is the session running right now.
Latest commits auto-updated from the repo
07 — Migration in Flight

The frontier: plugins written in Lua.

The endgame is authored plugins — including an in-game AI writing them. A pure-JS Lua VM (Fengari) runs sandboxed: the script sees only an engine table and nothing else. Crucially, no engine impact — the engine↔Lua contract is exactly the contract a JSON-op DSL would have used, so Lua just drops into the language layer as a swap.

What a whole behaviour looks like now
-- "This Is Fine": lob a gravity ember, light a fire where it lands
engine.behavior("drop", function(fire)
  local id = engine.spawn(fire.projectile, fire.aim_x, fire.aim_y)
  engine.set(id, "ember", {
    team  = engine.get(fire.shooter, "body", "team"),
    owner = fire.shooter,
  })
end)

Ids cross the boundary as opaque handles; component data never gets marshalled into Lua tables — the id is the handle, the data stays in the world. The plan rebases existing features onto the host first (if the jetpack can't move cleanly, new plugins won't either), then builds the Sandbox plugins on top.

Migration progress meters live from TASKS.md · checklist illustrative

⚠︎ One open honesty flag the plan keeps for itself: VM overhead is still unmeasured — the "keep it thin" discipline is asserted, not yet proven under a busy room. The escape hatch (compile Lua → JSON-ops later, same contract) is written down so it stays a data-driven call.

08 — What It's Becoming

A moddable sandbox engine wearing a meme shooter.

Read the wishlist and the shape is unmistakable: the meme shooter is the first game, not the product. "Game modes are actually games" — base services run any tile-based stickfigure game; the plugins you switch on are what the game is. The nearest destination is Sandbox Mode: pick a generator, toggle plugins.

🌗 The Sandbox stack

phase 3 · queued

Lighting → day/night cycle → "enemies spawn at night" → hunger & thirst. Each a plugin depending on the one below it.

🤖 AI-authored plugins

the whole point

Players safely use in-game AI to write Lua plugins on the fly, behind a permission filter of allowed events & service methods.

🐔 Chickens as mounts

bestiary

They eat seeds, maybe. Alongside cows that just moo (Earthworm Jim), laser rollerblade spiders, SAND WORMS.

🪚 Map hazards

world things

Circular-saw platforms, a rope-cut wrecking ball that swings as a pendulum, "Oops! All Raisins!" weather.

🧗 Adventure that learns

later

The one-ground march rightward through hordes — and eventually the game shapes itself to how you play.

🎯 Netcode endgame

roadmap

Client-side prediction + reconciliation, binary snapshot packing, area-of-interest culling.

And the wildest wishlist line, which tells you the sensibility: "a line of code that describes some function of the game, and if you shoot it, that function just stops happening."

09 — Health Report

Architecture health & trajectory.

An outside read on the codebase as of this snapshot — where it's strong, where the load-bearing risk sits, and where it's actually headed.

◆ Structural strengths

  • Data-as-truth is enforced, not aspirational. One JSON read by server, client & tools; check refuses to boot on a dangling reference.
  • Two DI systems, cleanly separated and explicitly protected in a written retro — the sim stays out of the service layer.
  • Unusual test discipline for the pace — 43 headless sims gate every commit; features arrive with their sim, not after.
  • Migrations are reasoned, not rushed — SOA, matter.js, then Lua each got a planning doc, a rationale, and an escape hatch before code.
  • Documentation is a genuine moat — README, TASKS, three plugin-architecture docs, a post-mortem retro. The why is recorded.

▲ Watch these

  • The Lua migration is mid-flight. Phase 1 is done, but the load-bearing pieces — generic emit points and the items plugin — aren't in. Incomplete migrations can ossify into two half-systems.
  • Plugin VM cost is unmeasured — flagged by the plan itself. "Thin" is a discipline, not yet a number under load.
  • Velocity outruns any review but the tests. 84 commits/day is safe only while the sim net stays ahead of the features.
  • Bus factor is 1. A single author/session; the exceptional docs are the mitigation.
  • No auth yet (acknowledged) — resource caps stand in. Fine for LAN co-op; a wall before public hosting.
Verdict
Healthy and accelerating. The architecture is deliberately ahead of the content — each re-platforming buys the next flood of features more cheaply than the last. The one thing to finish before starting anything new is the Lua cutover: land the emit points and the items plugin so the substrate is load-bearing, not a promising skeleton beside the real engine.