Meme Wars ยท Plugin System ยท Design

A plugin runs on the server.
How does it draw?

Every plugin so far has hit the same wall: it lives on the server, so it can't ship a pixel of client art. We've dodged it with a fixed icon palette โ€” a plugin picks icon = "fire" and a tint, and the client draws from a set it already knows. That's why the campfire is a generic flame and every crop is the same sprig. This lays out how to give plugins real, moving graphics โ€” anchored on the idea of a plugin shipping a client.lua, exactly like the Lua-driven canvas animations already running on ericdube.com.

the idea  plugins/<name>/client.lua reuses  Fengari + your lua-animations harness unblocks  campfire that flickers ยท custom entities ยท Set 12.5 graphics
01 ยท The proposal

Aclient.lua โ€” the plugin drawsfeatured

A directory plugin gains a second file: client.lua, loaded into a Fengari VM on the client. It registers a draw function per sprite name, using canvas primitives the engine provides โ€” the same fill_hsl / circle / line / fade vocabulary your banner animations use. The client calls it per entity, per frame, with the entity's snapshot fields. main.lua stays server-side game logic; client.lua is pure presentation. Neither can see the other's state โ€” the wire is still just scalars.

plugins/cooking/client.lua
-- register a sprite the server spawns as `campfire`
sprite("campfire", function(e, t)   -- e = snapshot fields, t = seconds
  local flick = 0.6 + 0.4 * math.sin(t * 9 + e.x)
  fill_rgb(74, 51, 25, 1)                  -- the logs
  rect(e.x - 10, e.y - 2, 20, 4)
  fill_hsl(28, 95, 55 + flick * 8, 0.9)     -- outer flame
  circle(e.x, e.y - 8 - flick * 3, 6 + flick)
  fill_hsl(48, 100, 80, 0.9)               -- hot core
  circle(e.x, e.y - 7, 2.6)
end)
what the player sees โ€” every frame
client.lua ยท flickers, throws sparks โ€” actually a campfire
Strength

Full procedural animation, authored in the same language and the same primitives you already use. A plugin becomes visually first-class โ€” the campfire flickers, a crop sways, a shield pulses.

Cost / risk

Fengari joins the client bundle (~your banner cost), and it's Lua per entity per frame โ€” needs a budget (only custom sprites; cache/throttle off-screen). Sandbox it like the server VM: canvas ops only, no DOM.

Fit

Highest. It's the exact model on ericdube.com, lifted into the game โ€” setup/draw per sprite instead of per banner.


The rest of the spectrum

Three lighter options to weigh it against

client.lua is the most powerful answer. These are what you'd reach for when you want less โ€” and a couple can live alongside it rather than instead of it.

BIcon palette โ€” pick from a set ยท today

The plugin names a client-provided icon and a tint; zero code, zero cost, a fixed vocabulary. It's the floor everything else builds above โ€” keep it as the effortless default for plugins that don't care.

-- in the item spec / server side
{ icon = "fire", tint = "#ff7a33" }
๐Ÿ”ฅ๐ŸŒฑ๐Ÿ–โ–ง

CDeclarative sprite โ€” shapes as data

The plugin ships a JSON sprite: a list of shapes with optional keyframe tweens the client interprets. No code to run, so no sandbox and no per-frame VM โ€” but it can only express what the format anticipates. A middle rung that client.lua makes largely redundant (Lua data is this, plus logic).

{ "shapes": [
  { "circle": [0,-8,6], "hsl":[28,95,55],
    "pulse": { "r":[6,8], "hz":2 } }
]}
{ }

DImage assets โ€” ship the art

The plugin ships PNGs (as files or data URIs); the client draws them, with an optional sprite-sheet for frame animation. Best for detailed, authored art a hand can't code โ€” worst for anything reactive. Pairs with client.lua: draw an image, then move/tint it in Lua (Set 12.5 wants this too).

-- client.lua can draw a shipped image too
image("campfire.png", e.x - 16, e.y - 28, 32, 32)

Recommendation

client.lua, with icons under it and images beside it

Your instinct is the right one โ€” client.lua is the answer, and it's cheap to reach because the harness already exists. Skip the declarative format (C); Lua subsumes it. Keep B as the default, add D so authored art has a home.

A path

  1. Port the harness. Lift your lua-animations Fengari setup into the client, sandboxed (canvas primitives only, no DOM/io), one VM per room's plugin set.
  2. Bind sprites to entities. A snapshot entity carries a sprite name (cooking:campfire); the client dispatches to that plugin's registered draw(e, t). Base-game sprites stay JS โ€” only plugin ones cross into Lua.
  3. Budget it. Only custom sprites run Lua; skip off-screen; cap draws per frame with a visible log if the cap bites. Measure before worrying.
  4. Add image(). Plugins ship art next to client.lua; Lua composes it (place, tint, animate).
  5. First win: the campfire. A cooking/client.lua flame retires the "campfire should look like a campfire" task and proves the whole path end-to-end.

A few calls I'd want your read on before building:

The API surfaceAdopt your exact primitives (fill_hsl, circle, line, rect, fade, line_widthโ€ฆ), or a slightly richer set (paths, gradients, transforms)? Starting with yours is the fast path; we grow it as sprites ask for more.
Per-sprite vs per-sceneRegister one draw per sprite (the engine positions it, like the icon path today), or hand a plugin a whole overlay layer to paint freely (like a banner)? Per-sprite is safer and composes with the camera; a free layer is more powerful and more rope.
Determinism & timeclient.lua is presentation only โ€” never touches the sim โ€” so its math.random and clock are free (no seed discipline). Confirm we're happy drawing purely from snapshot fields + wall-clock t.
BundleFengari client-side adds weight (you already pay it on the site). Ship it always, or lazy-load it the first time a room has a graphics plugin?
Meme Wars ยท plugin graphics โ€” design directions for review. Companion to the UI Framework and Inventory directions, and the Field Codex. Grounded in the lua-animations harness on ericdube.com. Nothing here is built yet.