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
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.
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.
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.
● 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.
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.
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.
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).
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.
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.
-- "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.
⚠︎ 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.
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."
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.