Skip to content
NewIntroducing myaku: a frame profiler that explains hitches in any web game

debuffmore creativity, less setup.

An open-source toolkit for 2D browser games. It handles the boring bits so you can get to the fun part sooner.

0 pointsswap two neighbours to line up three · full game · source

Empowering best-in-class 2D web games

Your idea deserves the first hour ​

Every game starts as a spark: a world to poke at, a jump that feels just right, a joke only you would make. Then the plumbing shows up. Game loop, crisp pixels, controls, sound, levels, assets, and figuring out why it broke. The spark can fizzle before it ever gets to play.

debuff does the plumbing. You get to tinker, wobble things, break them on purpose and keep the bits that make you grin.

From doodle to done ​

  1. StartA running game in under a minute, with a starter or a platformer.
  2. BuildSprites, levels from Tiled and LDtk, and an asset pipeline that ships only what you use.
  3. TuneFeel values become live sliders in the dev panel, with undo.
  4. VerifyDeterministic scenarios drive your game in a real browser and compare screenshots.
  5. ShipA size report that fails if dev code leaks. Web anywhere, or desktop with Tauri.

Five tiny snippets, one bouncy game ​

No spelunking through game.world.systems.physics.step(). A game is a scene with update and render, and the fun stuff is one call away.

ts
const game = new Game({ canvas, width: 320, height: 180 });
const input = new Input(game, {
  bindings: { pop: ["Space", "PadA", "Pointer"] },
});

game.add(play).go("play");
game.start();
ts
// Too floaty? Drag the slider in the dev panel until it feels right.
const tuning = game.params.define("player", {
  speed: { type: "number", default: 140, min: 0, max: 600, units: "px/s" },
});

player.vx = approach(player.vx, dir * tuning.values.speed, accel * dt);
ts
if (input.pressed("pop")) {
  // squish, sparkle, rumble, boop
  squash(tweens, player, 0.35);
  particles.emit({ x: player.x, y: player.y, count: 16, gravity: 200 });
  game.camera.shake(0.3);
  audio.synth({ freq: 660, freqEnd: 220, decay: 0.15 });
}
ts
import { expect, play, test } from "keiko";

test("never leaves the screen", async () => {
  const game = await play<State>();
  game.press("ArrowLeft");
  game.during("3s", (s) => s.player.x >= 8, "buff stays on screen");
});
json
{
  "steps": [
    { "press": "ArrowRight" },
    { "wait": 30 },
    { "release": "ArrowRight" },
    { "expect": { "state.player.x": { "gt": 200 } } },
    { "capture": "moved" }
  ]
}

Build your first game · See the full examples

The boring bits, handled ​

Play first, read later ​

The examples include a tilemap platformer, mossfell, a top-down adventure, squish, a match-3, and Overbuffed, a swarm survival game. They're all playable right in the browser, each with its source and the docs behind it.

Bring a coding agent along, if you like ​

Agents are welcome and never required. With the debuff MCP server, an agent like Claude Code can play the game you have open, read its state and explained errors, tune params and verify its changes with scenarios. Every action it takes shows in your dev panel, where you can undo it.

New projects ship an AGENTS.md with the commands and rules, so agents start with the right context. Creative calls stay with you: the tools are built for agents to propose and people to decide.

Made by people who make things ​

Built on the shoulders of Vite, Vitest, happy-dom, TypeScript, Tiled, LDtk and more open-source work. Thank you.

Contribute on GitHub · Join the Discord

Make something weird. ​