dotframe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/assets/audio/hit.mp3 +0 -0
  5. package/assets/audio/loop.mp3 +0 -0
  6. package/assets/crafter.png +0 -0
  7. package/assets/fonts/archivo-black.json +133 -0
  8. package/assets/fonts/archivo-black.png +0 -0
  9. package/assets/fonts/bangers.json +133 -0
  10. package/assets/fonts/bangers.png +0 -0
  11. package/assets/suzanne.glb +0 -0
  12. package/cli/commands/config.ts +35 -0
  13. package/cli/commands/doctor.ts +71 -0
  14. package/cli/commands/new.ts +73 -0
  15. package/cli/commands/play.ts +244 -0
  16. package/cli/commands/ship.ts +94 -0
  17. package/cli/commands/skills.ts +66 -0
  18. package/cli/commands/snap.ts +88 -0
  19. package/cli/lib.ts +148 -0
  20. package/cli/main.ts +95 -0
  21. package/cli/simkit.ts +152 -0
  22. package/cli/tsconfig.json +8 -0
  23. package/native/df_audio.c +266 -0
  24. package/native/df_native.c +605 -0
  25. package/native/ffi.macos.json +318 -0
  26. package/native/ffi.windows.json +322 -0
  27. package/native/third_party/dr_mp3.h +5430 -0
  28. package/native/third_party/stb_image.h +7988 -0
  29. package/native/third_party/stb_image_write.h +1724 -0
  30. package/native/third_party/stb_truetype.h +5079 -0
  31. package/package.json +47 -0
  32. package/scripts/build-native.sh +33 -0
  33. package/scripts/build-web.sh +10 -0
  34. package/scripts/vendor.sh +36 -0
  35. package/skills/assets/SKILL.md +11 -0
  36. package/skills/core/SKILL.md +51 -0
  37. package/skills/core/references/errors.md +22 -0
  38. package/skills/discord/SKILL.md +12 -0
  39. package/skills/export-web/SKILL.md +20 -0
  40. package/skills/game-design/SKILL.md +27 -0
  41. package/skills/game-design/references/sim-walkthrough.md +32 -0
  42. package/skills/ios/SKILL.md +19 -0
  43. package/skills/macos/SKILL.md +16 -0
  44. package/skills/netplay/SKILL.md +34 -0
  45. package/skills/relay/SKILL.md +18 -0
  46. package/src/audio.ts +19 -0
  47. package/src/draw2d.ts +901 -0
  48. package/src/ecs.ts +30 -0
  49. package/src/gltf.ts +82 -0
  50. package/src/gpu.ts +86 -0
  51. package/src/input.ts +215 -0
  52. package/src/math.ts +113 -0
  53. package/src/native/backend.ts +142 -0
  54. package/src/native/ffi.ts +42 -0
  55. package/src/native/library.ts +65 -0
  56. package/src/native/run.ts +88 -0
  57. package/src/physics.ts +57 -0
  58. package/src/platform.ts +12 -0
  59. package/src/raster2d.ts +348 -0
  60. package/src/render.ts +132 -0
  61. package/src/shapes.ts +37 -0
  62. package/src/sim.ts +56 -0
  63. package/src/storage.ts +6 -0
  64. package/src/web/run.ts +338 -0
  65. package/templates/_base/AGENTS.md +7 -0
  66. package/templates/_base/dotframe.json +15 -0
  67. package/templates/_base/gitignore +5 -0
  68. package/templates/_base/index.html +14 -0
  69. package/templates/_base/main.web.ts +27 -0
  70. package/templates/_base/package.json +12 -0
  71. package/templates/_base/sim.ts +31 -0
  72. package/templates/blank/game.ts +64 -0
  73. package/templates/fighter/game.ts +131 -0
  74. package/templates/platformer/game.ts +120 -0
  75. package/tools/check-audio.ts +57 -0
  76. package/tools/gen-library-glue.ts +83 -0
  77. package/tools/make-sprite.ts +92 -0
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "dotframe",
3
+ "version": "0.1.0",
4
+ "description": "TS-first game engine and agent-first CLI: one codebase for web, Discord, iOS and native binaries",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "devDependencies": {
8
+ "@scriptc/runtime-win32-x64-msvc": "0.2.0",
9
+ "@types/bun": "^1.4.2",
10
+ "@webgpu/types": "^0.1.74",
11
+ "typescript": "^7.0.2"
12
+ },
13
+ "bin": {
14
+ "dotframe": "cli/main.ts"
15
+ },
16
+ "files": [
17
+ "cli",
18
+ "src",
19
+ "skills",
20
+ "templates",
21
+ "assets",
22
+ "tools/*.ts",
23
+ "native",
24
+ "scripts",
25
+ "README.md",
26
+ "CHANGELOG.md",
27
+ "LICENSE"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/crafter-games/dotframe"
32
+ },
33
+ "homepage": "https://github.com/crafter-games/dotframe#readme",
34
+ "keywords": [
35
+ "game-engine",
36
+ "webgpu",
37
+ "typescript",
38
+ "scriptc",
39
+ "rollback",
40
+ "netplay",
41
+ "agent",
42
+ "cli"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }
@@ -0,0 +1,33 @@
1
+ #!/bin/sh
2
+ # Builds a native binary. Usage: scripts/build-native.sh <macos|windows> <example-name | path/to/main.native.ts> [out-name]
3
+ set -e
4
+ target=$1
5
+ entry=${2:-triangle}
6
+ root=$(cd "$(dirname "$0")/.." && pwd)
7
+ case $entry in
8
+ *.ts) entry_file=$(cd "$(dirname "$entry")" && pwd)/$(basename "$entry"); name=${3:-$(basename "$(dirname "$entry_file")")} ;;
9
+ *) entry_file="$root/examples/$entry/main.native.ts"; name=${3:-$entry} ;;
10
+ esac
11
+ mkdir -p "$root/build/$target"
12
+ case $target in
13
+ macos)
14
+ for unit in df_native df_audio; do
15
+ clang -O2 -c "$root/native/$unit.c" -I"$root/vendor/SDL3-3.4.16/include" -I"$root/vendor/wgpu/macos/include" \
16
+ -mmacosx-version-min=14.0 -o "$root/build/macos/$unit.o"
17
+ done
18
+ ar rcs "$root/build/macos/libdf_native.a" "$root/build/macos/df_native.o" "$root/build/macos/df_audio.o"
19
+ cd "$(dirname "$entry_file")" && scriptc build "$(basename "$entry_file")" --ffi "$root/native/ffi.macos.json" \
20
+ -o "$root/build/macos/$name"
21
+ ;;
22
+ windows)
23
+ for unit in df_native df_audio; do
24
+ zig cc -target x86_64-windows-gnu -O2 -c "$root/native/$unit.c" -I"$root/vendor/SDL3-3.4.16/include" \
25
+ -I"$root/vendor/wgpu/windows/include" -o "$root/build/windows/$unit.o"
26
+ done
27
+ zig ar rcs "$root/build/windows/libdf_native.a" "$root/build/windows/df_native.o" "$root/build/windows/df_audio.o"
28
+ cd "$(dirname "$entry_file")" && SCRIPTC_RUNTIME_PACK="$root/node_modules/@scriptc/runtime-win32-x64-msvc" \
29
+ SCRIPTC_TARGET=x86_64-windows-gnu scriptc build "$(basename "$entry_file")" --ffi "$root/native/ffi.windows.json" \
30
+ --windows-subsystem gui -o "$root/build/windows/$name.exe"
31
+ ;;
32
+ *) echo "usage: $0 <macos|windows> <example | path/to/main.native.ts> [out-name]" >&2; exit 2 ;;
33
+ esac
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ # Bundles an example for the browser. Usage: scripts/build-web.sh [example]
3
+ set -e
4
+ example=${1:-triangle}
5
+ root=$(cd "$(dirname "$0")/.." && pwd)
6
+ out="$root/build/web/$example"
7
+ mkdir -p "$out"
8
+ bun build "$root/examples/$example/main.web.ts" --outfile "$out/main.js" --target browser
9
+ cp "$root/examples/$example/index.html" "$out/"
10
+ mkdir -p "$out/assets" && cp -R "$root"/assets/. "$out/assets/"
@@ -0,0 +1,36 @@
1
+ #!/bin/sh
2
+ # Downloads wgpu-native prebuilts and builds SDL3 static for a target. Usage: scripts/vendor.sh <macos|windows>
3
+ set -e
4
+ target=$1
5
+ root=$(cd "$(dirname "$0")/.." && pwd)
6
+ wgpu_version=v29.0.1.1
7
+ sdl_version=3.4.16
8
+ vendor="$root/vendor"
9
+ mkdir -p "$vendor/build"
10
+
11
+ case $target in
12
+ macos) wgpu_asset=wgpu-macos-aarch64-release ;;
13
+ windows) wgpu_asset=wgpu-windows-x86_64-gnu-release ;;
14
+ *) echo "usage: $0 <macos|windows>" >&2; exit 2 ;;
15
+ esac
16
+
17
+ if [ ! -f "$vendor/wgpu/$target/lib/libwgpu_native.a" ]; then
18
+ mkdir -p "$vendor/wgpu/$target"
19
+ curl -fsSL "https://github.com/gfx-rs/wgpu-native/releases/download/$wgpu_version/$wgpu_asset.zip" -o "$vendor/wgpu/$target.zip"
20
+ unzip -oq "$vendor/wgpu/$target.zip" -d "$vendor/wgpu/$target"
21
+ rm "$vendor/wgpu/$target.zip"
22
+ fi
23
+
24
+ if [ ! -d "$vendor/SDL3-$sdl_version" ]; then
25
+ curl -fsSL "https://github.com/libsdl-org/SDL/releases/download/release-$sdl_version/SDL3-$sdl_version.tar.gz" | tar xz -C "$vendor"
26
+ fi
27
+
28
+ if [ ! -f "$vendor/build/sdl-$target/libSDL3.a" ]; then
29
+ case $target in
30
+ macos) extra="-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0" ;;
31
+ windows) extra="-DCMAKE_TOOLCHAIN_FILE=$vendor/toolchain/zig-windows.cmake" ;;
32
+ esac
33
+ cmake -S "$vendor/SDL3-$sdl_version" -B "$vendor/build/sdl-$target" -DCMAKE_BUILD_TYPE=Release \
34
+ -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF $extra
35
+ cmake --build "$vendor/build/sdl-$target" -j 4
36
+ fi
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: assets
3
+ description: Asset licensing and loading for dotframe games. Use when adding sprites, audio, or fonts, or before any release or store build.
4
+ ---
5
+ # assets
6
+
7
+ - Mark every asset you cannot distribute in `assets.localOnly` in dotframe.json (paths relative to the repo). `dotframe build <target> --release` fails with `ASSETS_LOCAL_ONLY` while any of them exists. Debug builds and local play are unaffected.
8
+ - To ship, replace the files with licensed ones (original art, CC0, or licensed packs), record the source and license next to them, then remove the path from `localOnly`.
9
+ - Fonts render through SDF atlases baked by `tools/bake-font.c`. Keep the font license (for example SIL OFL) with the atlas.
10
+ - Headless runs (`sim`, `replay`, `desync`) skip asset loading. If gameplay depends on asset data (sprite boxes, frame counts), load that data in headless too, or the sim will differ from the real game.
11
+ - `snap` loads assets from the game root over HTTP, so asset paths must be relative to the repo root.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: core
3
+ description: Core dotframe usage. Read before running any dotframe command: the edit, sim, snap, replay loop, the Sim contract, the trust ladder, and how to read errors.
4
+ ---
5
+ # dotframe core
6
+
7
+ dotframe is a TypeScript game engine. One codebase runs on the web (WebGPU), in Discord Activities, and as native macOS, Windows and iOS builds. The CLI lets an agent play the game without a screen: the simulation is deterministic, so the same seed and inputs always produce the same state.
8
+
9
+ ## The loop
10
+
11
+ 1. Edit game code.
12
+ 2. `dotframe sim --mash 7 --frames 600 --json`: run it headless and read `state` and `checksum`.
13
+ 3. `dotframe snap --frame 300 --mash 7 --out f300.png`: render one frame with the real WebGPU renderer, then open the PNG and look at it. The JSON state tells you what the simulation did; only the image tells you what a player sees.
14
+ 4. `dotframe replay verify replays/*.json`: golden replays must still pass. If a change is meant to alter gameplay, re-record them on purpose and say so.
15
+ 5. For online games, `dotframe desync` (see `dotframe skills get netplay`).
16
+ 6. Only then `dotframe build <target>`.
17
+
18
+ Never claim a visual change works from the JSON alone. Snap it and look.
19
+
20
+ ## Inputs
21
+
22
+ - `--mash <seed>`: random mashing players, new input every 6 frames. Good default for smoke tests.
23
+ - `--inputs file.jsonl`: one line per frame, `[p0, p1]`, or sparse `{"frame": 120, "inputs": [p0, p1]}` held until the next line. Each input is the game's JSON shape (for example `{"right": true, "attack": true}`) or an already-encoded number.
24
+ - `--seed <n>` seeds the simulation; `--options '<json>'` overrides match options (stage, characters, stocks).
25
+ - `--every <n>` on `sim` adds a checksum trace.
26
+
27
+ ## The Sim contract
28
+
29
+ `dotframe.json` names a module (`"sim": "sim.ts"`) whose default export is `defineSim({...})` from `dotframe/src/sim`: `players`, `window`, `options`, `neutral`, `encode`, `random`, and `create(platform)` returning `start`, `step`, `checksum`, `state`, `over`, `save`, `restore`, and optionally `inspect` and `render`. Rules that keep it honest:
30
+
31
+ - All simulation state changes only inside `step`. `render` reads, never writes (no spawning particles or texts from draw code).
32
+ - Randomness comes from a seeded generator that `save`/`restore` capture.
33
+ - `inspect` returns the whole state graph so `desync` can name the exact field that differs.
34
+
35
+ ## Trust ladder
36
+
37
+ | Level | Commands | Rule |
38
+ |---|---|---|
39
+ | Read | `sim`, `snap`, `replay verify`, `desync`, `doctor`, `config get`, `skills` | Run freely |
40
+ | Local write | `build`, `replay record`, `config set`, `new`, `dev`, `doctor --fix` | Run freely, report what changed |
41
+ | External | `deploy`, `relay deploy`, `device install` | Run `--dry-run` first, show the plan, rerun with `--yes` only after the human approves |
42
+
43
+ `--yes` is the human's approval, not yours. Never add it on your own.
44
+
45
+ ## Output and errors
46
+
47
+ Pass `--json` and read `ok`. Errors look like `{"ok": false, "error": {"code", "message", "fix", "skill"}}`. Apply `fix`, and when it is not obvious run `dotframe skills get <skill>`. Exit codes: 0 ok, 1 failure, 2 approval required. Full list: `dotframe skills get core --full`.
48
+
49
+ ## Other guides
50
+
51
+ `dotframe skills list`. Load the one that matches the task: netplay, export-web, discord, ios, macos, relay, game-design, assets.
@@ -0,0 +1,22 @@
1
+ # Error codes
2
+
3
+ | Code | Meaning | Fix |
4
+ |---|---|---|
5
+ | NO_CONFIG | No dotframe.json here or above | cd into the game, or `dotframe new` |
6
+ | BAD_CONFIG | dotframe.json does not parse | Fix the JSON |
7
+ | NO_SIM / SIM_MISSING / BAD_SIM | The sim module is not set, missing, or not `defineSim` | See the Sim contract in core |
8
+ | INPUTS_MISSING / BAD_INPUTS | Inputs file missing or a line has the wrong shape | One JSON array or `{frame, inputs}` per line, one input per player |
9
+ | BAD_OPTIONS | `--options` is not a JSON object | Quote it: `--options '{"stocks": 1}'` |
10
+ | UNKNOWN_TARGET | Target not in dotframe.json | Use a listed target or add one |
11
+ | STEP_FAILED | A build step exited non-zero | Read the step output; `dotframe doctor` |
12
+ | ASSETS_LOCAL_ONLY | A release would ship unlicensed assets | See `assets` |
13
+ | APPROVAL_REQUIRED (exit 2) | External action without `--yes` | Show the `--dry-run` plan to the human |
14
+ | DEPLOY_SOURCE_DIR | Deploy dir looks like source (repo root, .git, dotframe.json) | Point `out` at the build folder |
15
+ | NOT_BUILT | Artifact missing | `dotframe build <target>` |
16
+ | CONFIG_PLACEHOLDER | Template value never filled in | `dotframe config set ...` |
17
+ | DEPLOY_FAILED / INSTALL_FAILED | Provider command failed | Message carries the provider output |
18
+ | TOOL_MISSING | A required CLI is not installed | `dotframe doctor` lists the install command |
19
+ | SNAP_TIMEOUT / SNAP_PAGE_ERROR / BROWSER_FAILED | The snap page did not report ready | Usually no WebGPU in the browser; see message |
20
+ | UNSUPPORTED | The command does not apply (e.g. desync on a 1-player sim) | |
21
+ | REPLAY_MISSING | Replay file not found | `dotframe replay record` |
22
+ | UNKNOWN_SKILL / UNKNOWN_TEMPLATE / UNKNOWN_COMMAND | Typo | Read the `fix` list |
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: discord
3
+ description: Run a dotframe web build as a Discord Activity. Use when setting up or debugging a Discord Activity, its URL mappings, the relay path, or stale bundles inside Discord.
4
+ ---
5
+ # discord
6
+
7
+ A Discord Activity is the web build served through Discord's proxy, so `dotframe build discord` and `dotframe deploy discord` use the web pipeline (see `export-web`).
8
+
9
+ - URL mappings in the Discord developer portal: `/` to the web deployment, `/relay` to the relay. The game connects to `wss://<activity host>/relay`, never to an outside host (the proxy blocks it).
10
+ - The room is the Activity instance id, so everyone in the same launch meets.
11
+ - Discord caches aggressively. Hashed bundle names plus a no-cache `index.html` fix it; if a change does not show up, check the bundle hash in the served page first.
12
+ - Test 1P vs CPU first, then online. Online quality is bound by relay latency (see `relay`); run `dotframe desync --latency <measured ping>` before blaming the netcode.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: export-web
3
+ description: Build and deploy the web target of a dotframe game. Use when bundling for the browser, serving locally, or deploying to Vercel.
4
+ ---
5
+ # export-web
6
+
7
+ ```sh
8
+ dotframe build web --json # runs targets.web.steps
9
+ dotframe dev # build, serve on :5173, rebuild on change
10
+ dotframe deploy web --dry-run # show the plan
11
+ dotframe deploy web --prod --yes # only after the human approved the plan
12
+ ```
13
+
14
+ ## Rules
15
+
16
+ - Deploy only the built folder (`targets.web.out`). The CLI refuses a folder that looks like source (repo root, `.git`, `dotframe.json`): that mistake once put the wrong game in production.
17
+ - Do not connect the repo to Vercel's Git integration for these games. A push deploys the repo root.
18
+ - Content-hash the bundle (`main.<hash>.js`) and serve `index.html` with `Cache-Control: no-cache`, so browsers and Discord's proxy never run a stale build.
19
+ - `deploy` without `--prod` makes a preview. Prefer it for checks.
20
+ - Verify a deploy by opening the URL with agent-browser and taking a screenshot, not by the exit code.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: game-design
3
+ description: Start and structure a new dotframe game. Use when scaffolding a game, choosing a template, or organizing game state so it stays deterministic and testable.
4
+ ---
5
+ # game-design
6
+
7
+ ```sh
8
+ dotframe new my-game --template fighter # or platformer, blank
9
+ cd my-game && dotframe sim --mash 7 --json && dotframe dev
10
+ ```
11
+
12
+ Templates: `fighter` (two players, knockback, stocks), `platformer` (one player, one-way platforms, coins), `blank` (two moving squares). Each one has:
13
+
14
+ - `src/game.ts`: pure state plus `createGame`, `step`, `render`, `encode`, `checksum`, `state`, `snapshot`.
15
+ - `sim.ts`: the Sim contract over game.ts. The CLI drives it.
16
+ - `main.web.ts`: keyboard to encoded input, fixed 60 Hz steps, render.
17
+ - `dotframe.json`, `AGENTS.md`, a stub skill, `replays/`.
18
+
19
+ ## Shape the game for agents
20
+
21
+ - Keep all state in one plain object graph. Then `structuredClone` is a snapshot and `inspect` is free.
22
+ - Encode input as a small number per player. Replays, netplay and mashing all reuse it.
23
+ - Make `state()` the summary a reviewer needs: positions, scores, who won. Agents read it after every sim.
24
+ - Record a golden replay as soon as a mechanic works (`dotframe replay record replays/<name>.json --mash 7`) and keep `bun test` running them.
25
+ - Add `--options` for anything you want to test in isolation (stage, character, one stock).
26
+
27
+ See `dotframe skills get game-design --full` for a sim.ts walkthrough.
@@ -0,0 +1,32 @@
1
+ # sim.ts walkthrough
2
+
3
+ ```ts
4
+ import { defineSim } from "dotframe/src/sim";
5
+ import { checksum, createGame, encode, randomInput, render, snapshot, state, step, WINDOW, PLAYERS } from "./src/game";
6
+
7
+ export default defineSim({
8
+ players: PLAYERS,
9
+ window: WINDOW,
10
+ options: {}, // defaults merged under --options
11
+ neutral: 0, // encoded "nothing pressed"
12
+ encode, // JSON input -> number
13
+ random: randomInput, // used by --mash
14
+ create: () => {
15
+ let game = createGame(1);
16
+ return {
17
+ ready: Promise.resolve(), // load assets here when not headless
18
+ start: (seed) => { game = createGame(seed); },
19
+ step: (inputs) => step(game, inputs),
20
+ checksum: () => checksum(game),
21
+ state: () => state(game),
22
+ over: () => game.winner >= 0,
23
+ save: () => snapshot(game),
24
+ restore: (s) => { Object.assign(game, snapshot(s)); },
25
+ inspect: () => game,
26
+ render: (draw) => render(game, draw),
27
+ };
28
+ },
29
+ });
30
+ ```
31
+
32
+ A game with classes, closures, or GPU handles in its state (like Crafter Smash) needs an identity-preserving snapshotter instead of structuredClone, and a `ready` promise that loads assets onto `platform.gpu` and `platform.draw` when `platform.headless` is false.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: ios
3
+ description: Build and install the iOS target of a dotframe game. Use when building for iPhone, fixing vendor links, signing, or installing on a device.
4
+ ---
5
+ # ios
6
+
7
+ ```sh
8
+ dotframe doctor --json # vendor links, scriptc, xcodegen, xcodebuild
9
+ dotframe doctor --fix # creates missing vendor symlinks
10
+ dotframe build ios --json # glue, scriptc library, xcodegen, xcodebuild
11
+ dotframe device install ios --dry-run
12
+ dotframe device install ios --yes # after approval; phone unlocked and trusted
13
+ ```
14
+
15
+ - The iOS build needs SDL3 and wgpu-native iOS builds under `vendor/dotframe/vendor/`. They are local symlinks (`links` in dotframe.json), not in git. `doctor --fix` creates them when the targets exist.
16
+ - Signing uses the team in the Xcode project; `-allowProvisioningUpdates` lets xcodebuild fetch profiles. A locked password manager can fail signing with `failed to fill whole buffer`: ask the human to unlock it and retry.
17
+ - `targets.ios.app` is the built `.app`; `targets.ios.device` comes from `xcrun devicectl list devices`.
18
+ - `build ios --release` refuses assets marked local-only (see `assets`). Debug builds for your own phone are fine.
19
+ - Confirm on the device by asking the human, or with a screenshot if available. A successful install is not a working game.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: macos
3
+ description: Build and run native macOS (and Windows) binaries of a dotframe game with scriptc. Use when compiling natively, benchmarking, or debugging the native backend.
4
+ ---
5
+ # macos
6
+
7
+ ```sh
8
+ dotframe build macos --json
9
+ ```
10
+
11
+ The step runs dotframe's `scripts/build-native.sh macos <entry> <name>`: it compiles the C shim (SDL3 + wgpu-native) and the game with scriptc into one binary with no JavaScript engine. Windows cross-builds with zig (`build-native.sh windows`).
12
+
13
+ - Games may need env vars at run time (Crafter Smash: `CHARS=railly,anthony SMASH_ROOT=<repo> DOTFRAME=<repo>/vendor/dotframe`).
14
+ - Benchmarks mean nothing while other processes load the machine. Check `top` first and say what else was running.
15
+ - Hidden or occluded windows skip frames and can spin a core. Keep test windows visible.
16
+ - scriptc compiles a subset of TypeScript: structural types become record copies, so engine backends are plain objects of functions, not classes behind interfaces.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: netplay
3
+ description: Rollback netplay for dotframe games. Use when building or debugging online play, a desync, rollbacks, input delay, or render code that might change simulation state.
4
+ ---
5
+ # netplay
6
+
7
+ dotframe online play is rollback netcode: each peer predicts the remote input (repeat the last one), simulates ahead, and when the real input arrives and differs, restores a snapshot and resimulates. It only works if every peer computes bit-identical state from the same inputs.
8
+
9
+ ## Test before going online
10
+
11
+ ```sh
12
+ dotframe desync --latency 120ms --jitter 30ms --frames 3000 --mash 7 --json
13
+ ```
14
+
15
+ It runs a reference simulation and two peers over a deterministic simulated link. Peer 1 also renders 3 times per step (`--renders`), like a 144 Hz screen. Read per peer:
16
+
17
+ - `firstDivergentFrame`: the checksum differed. A rollback or determinism bug.
18
+ - `firstStateDifference`: `{frame, path}` of the first field that differs at a checkpoint (`--every 30`). Catches state the checksum does not cover. Needs `inspect()` in the sim.
19
+ - `rollbacks`, `maxRollbackFrames`: how hard the link worked. Keep max under the game's rollback window.
20
+
21
+ Run several seeds and a high latency (474 ms is what a transatlantic relay measured). A pass on one seed proves little.
22
+
23
+ ## Reading a failure
24
+
25
+ - Only peer 1 differs: rendering changes state. Look for draw code that spawns effects, advances timers, moves the camera, or consumes the seeded random. Move it into `step`.
26
+ - Both peers differ: `save`/`restore` misses state, or something non-deterministic (Math.random, Date, Map iteration over unordered keys, floating state touched outside step).
27
+ - The path is the lead. `state.fx.texts.0.x: extra` means a text exists on one side only: find who pushes into `fx.texts` outside step.
28
+
29
+ ## Rules
30
+
31
+ - The camera, effects, and HUD timers are simulation state if the simulation ever reads them. Update them once per step, never per drawn frame.
32
+ - Render must restore any random state it uses, or use a separate visual generator.
33
+ - Input delay (`--delay`, default 2) trades latency for fewer rollbacks.
34
+ - After a fix, rerun desync across seeds, then `dotframe replay verify`, then test a real match.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: relay
3
+ description: Deploy and place the WebSocket relay that pairs netplay peers. Use when online play is laggy, choosing a relay region, or redeploying the relay.
4
+ ---
5
+ # relay
6
+
7
+ The relay forwards inputs between peers in a room. It does no simulation, so its only job is to be close to the players: every input crosses peer to relay to peer.
8
+
9
+ ```sh
10
+ dotframe relay deploy --dry-run
11
+ dotframe relay deploy --yes # dokploy: redeploys the compose stack
12
+ dotframe relay deploy --region eze --yes # fly: Buenos Aires; scl is Santiago
13
+ ```
14
+
15
+ - Providers live in `relay` in dotframe.json: `{"provider": "dokploy", "compose": "<id>"}` or `{"provider": "fly", "app": "...", "config": "fly.toml", "region": "eze"}`. Dokploy runs on one fixed VPS and ignores regions.
16
+ - Measure before and after: in-game ping, rollbacks per minute, stalls. A relay in Europe gave 474 ms between two players in South America.
17
+ - Dokploy applies compose domain changes only on redeploy.
18
+ - After moving the relay, update the Discord `/relay` URL mapping if the host changed.
package/src/audio.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Backend-agnostic audio: decoded sound effects, square tones and one streamed music track.
2
+
3
+ // Synchronous playback controls, usable in scriptc library mode (iOS) where promises are unavailable.
4
+ export interface AudioPlayer {
5
+ // rate scales pitch and speed together, like AudioBufferSourceNode.playbackRate.
6
+ play: (sound: number, volume: number, rate: number) => void;
7
+ // Square wave decaying exponentially to silence over duration seconds.
8
+ tone: (frequency: number, duration: number, volume: number) => void;
9
+ playMusic: (track: number, loop: boolean, volume: number) => void;
10
+ stopMusic: () => void;
11
+ pauseMusic: (paused: boolean) => void;
12
+ setMusicVolume: (volume: number) => void;
13
+ setMasterVolume: (volume: number) => void;
14
+ }
15
+
16
+ export interface Audio extends AudioPlayer {
17
+ loadSound: (mp3: Uint8Array) => Promise<number>;
18
+ loadMusic: (mp3: Uint8Array) => Promise<number>;
19
+ }