dotframe
0.3.3 · Canvas 2D fallback for phones

The game engine your agent can play.

Write the game once in TypeScript. Ship it to the web, iPhone, macOS, Windows and Discord. Every frame is deterministic, so your agent can play it headless, look at any frame, and find the bug before your players do.

$ npm i -g dotframe

then tell your agent: dotframe skills get core

frame 0000checksum 55d8c60a★ 0input ▲

Drag it back. Same frame, same checksum, every time.

f001how an agent plays

No screen needed. Until it wants one.

The sim is pure state and a fixed step. An agent mashes inputs through 600 frames in a millisecond and reads the state back as JSON. When it needs to see, it renders that exact frame through the real WebGPU renderer and opens the PNG.

Record a run once and it becomes the test: every change after that has to land on the same checksums. That is how a spill that kept taking lives in a loop showed up, at lives: -12.

Frame 600 of Espresso perfecto, rendered by dotframe snap

snap-600.png
what the agent saw at frame 600

$ dotframe sim --mash 7 --frames 600 --json
{  "ok": true,  "data": {    "frames": 505,    "over": true,    "checksum": 661060014,    "state": {      "phase": "done",      "served": 3,      "drink": "espresso",      "lives": 0,      "verdict": "MUY LEJOS",      …    },    "ms": 1  }}

f002the whole contract

game.ts · dotframe new my-game --template blank
export function step(game: Game, inputs: number[]): void {  game.frame += 1;  game.players.forEach((p, i: number): void => {    const bits = inputs[i] ?? 0;    p.x = Math.max(20, Math.min(WINDOW.width - 20, p.x + (bits & 2 ? 4 : 0) - (bits & 1 ? 4 : 0)));    p.y = Math.max(20, Math.min(WINDOW.height - 20, p.y - (bits & 4 ? 4 : 0) + 1));  });}

State in, inputs in, next frame out.

All the state lives in one object and only changes in step(). That is what lets dotframe replay it, snapshot it, and roll it back for netplay. Render only reads.

f003one codebase, five places

Native builds have no JavaScript engine inside.

scriptc compiles your TypeScript to a real binary. The web build runs the same code on WebGPU.

  • Web

    WebGPU, and Canvas 2D on phones without it

    dotframe build web
  • iPhone

    a native app, no web view

    dotframe build ios
  • macOS

    one binary, SDL3 + wgpu-native

    dotframe build macos
  • Windows

    cross-built from your Mac

    build-native.sh windows
  • Discord

    an Activity in the voice channel

    dotframe build discord

f004made with dotframe

f005shipped one?

Wear the badge.

Put it in your README so the next person finds the engine.

Made with dotframe
README.md
<a href="https://dotframe.crafter.games">  <picture>    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/crafter-games/dotframe/main/assets/badge/made-with-dotframe-dark.svg">    <img alt="Made with dotframe" src="https://raw.githubusercontent.com/crafter-games/dotframe/main/assets/badge/made-with-dotframe-light.svg" height="32">  </picture></a>