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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/assets/audio/hit.mp3 +0 -0
- package/assets/audio/loop.mp3 +0 -0
- package/assets/crafter.png +0 -0
- package/assets/fonts/archivo-black.json +133 -0
- package/assets/fonts/archivo-black.png +0 -0
- package/assets/fonts/bangers.json +133 -0
- package/assets/fonts/bangers.png +0 -0
- package/assets/suzanne.glb +0 -0
- package/cli/commands/config.ts +35 -0
- package/cli/commands/doctor.ts +71 -0
- package/cli/commands/new.ts +73 -0
- package/cli/commands/play.ts +244 -0
- package/cli/commands/ship.ts +94 -0
- package/cli/commands/skills.ts +66 -0
- package/cli/commands/snap.ts +88 -0
- package/cli/lib.ts +148 -0
- package/cli/main.ts +95 -0
- package/cli/simkit.ts +152 -0
- package/cli/tsconfig.json +8 -0
- package/native/df_audio.c +266 -0
- package/native/df_native.c +605 -0
- package/native/ffi.macos.json +318 -0
- package/native/ffi.windows.json +322 -0
- package/native/third_party/dr_mp3.h +5430 -0
- package/native/third_party/stb_image.h +7988 -0
- package/native/third_party/stb_image_write.h +1724 -0
- package/native/third_party/stb_truetype.h +5079 -0
- package/package.json +47 -0
- package/scripts/build-native.sh +33 -0
- package/scripts/build-web.sh +10 -0
- package/scripts/vendor.sh +36 -0
- package/skills/assets/SKILL.md +11 -0
- package/skills/core/SKILL.md +51 -0
- package/skills/core/references/errors.md +22 -0
- package/skills/discord/SKILL.md +12 -0
- package/skills/export-web/SKILL.md +20 -0
- package/skills/game-design/SKILL.md +27 -0
- package/skills/game-design/references/sim-walkthrough.md +32 -0
- package/skills/ios/SKILL.md +19 -0
- package/skills/macos/SKILL.md +16 -0
- package/skills/netplay/SKILL.md +34 -0
- package/skills/relay/SKILL.md +18 -0
- package/src/audio.ts +19 -0
- package/src/draw2d.ts +901 -0
- package/src/ecs.ts +30 -0
- package/src/gltf.ts +82 -0
- package/src/gpu.ts +86 -0
- package/src/input.ts +215 -0
- package/src/math.ts +113 -0
- package/src/native/backend.ts +142 -0
- package/src/native/ffi.ts +42 -0
- package/src/native/library.ts +65 -0
- package/src/native/run.ts +88 -0
- package/src/physics.ts +57 -0
- package/src/platform.ts +12 -0
- package/src/raster2d.ts +348 -0
- package/src/render.ts +132 -0
- package/src/shapes.ts +37 -0
- package/src/sim.ts +56 -0
- package/src/storage.ts +6 -0
- package/src/web/run.ts +338 -0
- package/templates/_base/AGENTS.md +7 -0
- package/templates/_base/dotframe.json +15 -0
- package/templates/_base/gitignore +5 -0
- package/templates/_base/index.html +14 -0
- package/templates/_base/main.web.ts +27 -0
- package/templates/_base/package.json +12 -0
- package/templates/_base/sim.ts +31 -0
- package/templates/blank/game.ts +64 -0
- package/templates/fighter/game.ts +131 -0
- package/templates/platformer/game.ts +120 -0
- package/tools/check-audio.ts +57 -0
- package/tools/gen-library-glue.ts +83 -0
- package/tools/make-sprite.ts +92 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First npm release: the engine plus the agent-first `dotframe` CLI.
|
|
6
|
+
|
|
7
|
+
- `sim`, `snap`, `replay record/verify`, `desync`: play a game headless or render a frame through WebGPU, deterministically, through the Sim contract in `src/sim.ts`.
|
|
8
|
+
- `desync` compares the full state graph (`inspect()`) at checkpoints and names the first differing path, so render code that changes simulation state shows up before a real match.
|
|
9
|
+
- `build`, `deploy`, `relay deploy`, `device install`, `doctor`, `config`: targets live in `dotframe.json`; external actions need `--yes` and preview with `--dry-run`.
|
|
10
|
+
- `skills list/get/path`: nine guides bundled with the CLI version (core, netplay, export-web, discord, ios, macos, relay, game-design, assets).
|
|
11
|
+
- `new` with fighter, platformer, and blank templates, and `dev`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Railly Hugo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# dotframe
|
|
2
|
+
|
|
3
|
+
TS-first game engine. One TypeScript codebase runs on the web (WebGPU) and compiles to native macOS and Windows binaries with [scriptc](https://scriptc.dev), with no JavaScript engine in the binary.
|
|
4
|
+
|
|
5
|
+
Status: S5 in progress (porting Crafter Smash). `examples/canvas2d` exercises `src/draw2d.ts`, an immediate-mode 2D layer modeled on Canvas2D (transforms, alpha, concave paths, arcs, strokes, PNG sprites) so Canvas2D games port mechanically. Before that, S4: `examples/smash-lite` is a two-player platform fighter: keyboard and gamepad input, fixed-step AABB physics, hitstun, knockback that scales with damage, and KOs. `DF_DEMO=1` (native) or `?demo` (web) lets scripted players fight, which is how CI captures it. Earlier slices: `examples/suzanne` loads a glTF mesh, spawns three entities in a minimal ECS and renders them lit with depth and a perspective camera. The same game code renders in the browser (WebGPU) and in native macOS and Windows binaries (SDL3 + wgpu-native through a flat C shim). CI runs both binaries and captures them with [cuse](https://github.com/crafter-agents/cuse).
|
|
6
|
+
|
|
7
|
+
## CLI
|
|
8
|
+
|
|
9
|
+
`dotframe` is an agent-first CLI over the engine. The simulation is deterministic, so an agent can play a game without a screen, render a frame with the real renderer, and catch netplay desyncs before a real match.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun link # or: bun add -g github:crafter-games/dotframe
|
|
13
|
+
dotframe new my-game --template fighter # fighter, platformer, blank
|
|
14
|
+
dotframe sim --mash 7 --frames 600 --json # headless run: state + checksum
|
|
15
|
+
dotframe snap --frame 300 --mash 7 # one frame through WebGPU (agent-browser)
|
|
16
|
+
dotframe replay record replays/a.json --mash 7 && dotframe replay verify replays/*.json
|
|
17
|
+
dotframe desync --latency 120ms --jitter 30ms
|
|
18
|
+
dotframe build web|ios|macos|discord
|
|
19
|
+
dotframe deploy web --prod --dry-run # external actions need --yes
|
|
20
|
+
dotframe doctor --fix
|
|
21
|
+
dotframe skills list # guides bundled with this version
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A game opts in with a `dotframe.json` (targets, deploy, relay, vendor links, local-only assets) and a `sim` module that default-exports `defineSim` from `src/sim.ts`. Agents start with `dotframe skills get core`. Every command takes `--json`; errors carry a `code`, a `fix`, and the `skill` to read.
|
|
25
|
+
|
|
26
|
+
- `cli/`: the CLI (Bun). `skills/`: the guides served by `dotframe skills`. `templates/`: the `dotframe new` templates.
|
|
27
|
+
|
|
28
|
+
## Layout
|
|
29
|
+
|
|
30
|
+
- `native/df_native.c`: flat C ABI over SDL3 and wgpu-native. scriptc FFI accepts scalars and byte spans only, so the shim owns every WebGPU struct.
|
|
31
|
+
- `src/gpu.ts`: backend-agnostic types for game code. Backends are plain objects of functions, because scriptc compiles structural types as record copies and a class cannot stand in for an interface.
|
|
32
|
+
- `src/math.ts`, `src/ecs.ts`, `src/gltf.ts`, `src/render.ts`: column-major matrices, a Map-per-component ECS, a `.glb` reader (positions, normals, indices) and a lit mesh renderer.
|
|
33
|
+
- `src/input.ts`, `src/physics.ts`, `src/shapes.ts`: engine key ids mapped per backend (SDL scancodes, `KeyboardEvent.code`, first gamepad), side-view AABB physics, and a unit box mesh.
|
|
34
|
+
- `src/draw2d.ts`: Canvas2D-style layer batched into one vertex buffer, one draw per texture. Setters replace property assignment (`setFillStyle` for `ctx.fillStyle =`). PNGs decode with stb_image natively and `createImageBitmap` on the web.
|
|
35
|
+
- `src/audio.ts`, `native/df_audio.c`: sound effects (MP3, volume, playback rate), square tones and one streamed music track. Native mixes in C over SDL3 audio with dr_mp3; the web uses Web Audio and an `<audio>` element. `examples/audio-check` plays a known timeline that CI records with SDL's disk driver and verifies with `tools/check-audio.ts` (segments, 880 Hz tone, 262 Hz music, silence after stop).
|
|
36
|
+
- `test/`: `bun test` covers color parsing, triangulation and physics (landing, walls, ceilings, falling off edges).
|
|
37
|
+
- `src/native`: FFI declarations and the native run loop. `native/ffi.{macos,windows}.json` bind them for every example.
|
|
38
|
+
- `src/web`: the browser run loop over `navigator.gpu`.
|
|
39
|
+
- `examples/*`: `game.ts` is shared; `main.native.ts` and `main.web.ts` pick the backend.
|
|
40
|
+
- `assets/audio/*.mp3`: original test sounds generated with ffmpeg.
|
|
41
|
+
- `assets/fonts/*`: Bangers and Archivo Black (SIL OFL, see `tools/fonts/`) baked into SDF atlases by `tools/bake-font.c`.
|
|
42
|
+
- `assets/crafter.png`: original 16x16 two-frame sprite, generated by `tools/make-sprite.ts`.
|
|
43
|
+
- `assets/suzanne.glb`: Blender's Suzanne from the Khronos glTF sample assets (CC0, Norbert Nopper / UX3D), packed geometry-only.
|
|
44
|
+
- `scripts/build-native.sh`: builds an example for `macos` or `windows`.
|
|
45
|
+
|
|
46
|
+
## Vendored dependencies (not committed)
|
|
47
|
+
|
|
48
|
+
- wgpu-native `v29.0.1.1` prebuilt (`wgpu-macos-aarch64-release`, `wgpu-windows-x86_64-gnu-release`) in `vendor/wgpu/{macos,windows}`.
|
|
49
|
+
- SDL3 `3.4.16` source in `vendor/SDL3-3.4.16`, built static into `vendor/build/sdl-{macos,windows}`. Windows is cross-built with zig via `vendor/toolchain/zig-windows.cmake`.
|
|
50
|
+
|
|
51
|
+
## Build
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
scripts/build-native.sh macos triangle
|
|
55
|
+
scripts/build-native.sh windows triangle # needs zig and @scriptc/runtime-win32-x64-msvc
|
|
56
|
+
scripts/build-web.sh triangle # then serve build/web/triangle
|
|
57
|
+
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"size": 64,
|
|
3
|
+
"ascent": 51.6471,
|
|
4
|
+
"descent": -12.3529,
|
|
5
|
+
"lineGap": 0,
|
|
6
|
+
"distanceRange": 8,
|
|
7
|
+
"glyphs": [
|
|
8
|
+
{ "code": 32, "x": 0, "y": 0, "width": 0, "height": 0, "offsetX": 0, "offsetY": 0, "advance": 19.5882 },
|
|
9
|
+
{ "code": 33, "x": 1, "y": 0, "width": 31, "height": 57, "offsetX": -6, "offsetY": -49, "advance": 19.5882 },
|
|
10
|
+
{ "code": 34, "x": 33, "y": 0, "width": 39, "height": 34, "offsetX": -5, "offsetY": -49, "advance": 29.4118 },
|
|
11
|
+
{ "code": 35, "x": 73, "y": 0, "width": 53, "height": 59, "offsetX": -7, "offsetY": -50, "advance": 38.8235 },
|
|
12
|
+
{ "code": 36, "x": 127, "y": 0, "width": 49, "height": 65, "offsetX": -4, "offsetY": -53, "advance": 39.2353 },
|
|
13
|
+
{ "code": 37, "x": 177, "y": 0, "width": 70, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 58.8235 },
|
|
14
|
+
{ "code": 38, "x": 248, "y": 0, "width": 65, "height": 59, "offsetX": -4, "offsetY": -50, "advance": 52.2941 },
|
|
15
|
+
{ "code": 39, "x": 314, "y": 0, "width": 26, "height": 34, "offsetX": -5, "offsetY": -49, "advance": 16.3529 },
|
|
16
|
+
{ "code": 40, "x": 341, "y": 0, "width": 34, "height": 70, "offsetX": -5, "offsetY": -52, "advance": 22.8824 },
|
|
17
|
+
{ "code": 41, "x": 376, "y": 0, "width": 34, "height": 70, "offsetX": -6, "offsetY": -52, "advance": 22.8824 },
|
|
18
|
+
{ "code": 42, "x": 411, "y": 0, "width": 39, "height": 39, "offsetX": -3, "offsetY": -49, "advance": 32.7059 },
|
|
19
|
+
{ "code": 43, "x": 451, "y": 0, "width": 48, "height": 48, "offsetX": -5, "offsetY": -40, "advance": 38.8235 },
|
|
20
|
+
{ "code": 44, "x": 500, "y": 0, "width": 30, "height": 39, "offsetX": -5, "offsetY": -20, "advance": 19.5882 },
|
|
21
|
+
{ "code": 45, "x": 531, "y": 0, "width": 32, "height": 26, "offsetX": -6, "offsetY": -29, "advance": 19.5882 },
|
|
22
|
+
{ "code": 46, "x": 564, "y": 0, "width": 30, "height": 28, "offsetX": -5, "offsetY": -20, "advance": 19.5882 },
|
|
23
|
+
{ "code": 47, "x": 595, "y": 0, "width": 34, "height": 63, "offsetX": -9, "offsetY": -52, "advance": 16.3529 },
|
|
24
|
+
{ "code": 48, "x": 630, "y": 0, "width": 51, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
25
|
+
{ "code": 49, "x": 682, "y": 0, "width": 47, "height": 57, "offsetX": -2, "offsetY": -49, "advance": 39.2353 },
|
|
26
|
+
{ "code": 50, "x": 730, "y": 0, "width": 51, "height": 58, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
27
|
+
{ "code": 51, "x": 782, "y": 0, "width": 51, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
28
|
+
{ "code": 52, "x": 834, "y": 0, "width": 53, "height": 58, "offsetX": -7, "offsetY": -50, "advance": 39.2353 },
|
|
29
|
+
{ "code": 53, "x": 888, "y": 0, "width": 51, "height": 58, "offsetX": -6, "offsetY": -49, "advance": 39.2353 },
|
|
30
|
+
{ "code": 54, "x": 940, "y": 0, "width": 52, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
31
|
+
{ "code": 55, "x": 0, "y": 71, "width": 51, "height": 57, "offsetX": -6, "offsetY": -49, "advance": 39.2353 },
|
|
32
|
+
{ "code": 56, "x": 52, "y": 71, "width": 51, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
33
|
+
{ "code": 57, "x": 104, "y": 71, "width": 51, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 39.2353 },
|
|
34
|
+
{ "code": 58, "x": 156, "y": 71, "width": 30, "height": 48, "offsetX": -5, "offsetY": -40, "advance": 19.5882 },
|
|
35
|
+
{ "code": 59, "x": 187, "y": 71, "width": 30, "height": 59, "offsetX": -5, "offsetY": -40, "advance": 19.5882 },
|
|
36
|
+
{ "code": 60, "x": 218, "y": 71, "width": 48, "height": 49, "offsetX": -5, "offsetY": -40, "advance": 38.8235 },
|
|
37
|
+
{ "code": 61, "x": 267, "y": 71, "width": 48, "height": 38, "offsetX": -5, "offsetY": -35, "advance": 38.8235 },
|
|
38
|
+
{ "code": 62, "x": 316, "y": 71, "width": 47, "height": 49, "offsetX": -4, "offsetY": -40, "advance": 38.8235 },
|
|
39
|
+
{ "code": 63, "x": 364, "y": 71, "width": 48, "height": 58, "offsetX": -6, "offsetY": -50, "advance": 35.9412 },
|
|
40
|
+
{ "code": 64, "x": 413, "y": 71, "width": 58, "height": 67, "offsetX": -7, "offsetY": -50, "advance": 43.5294 },
|
|
41
|
+
{ "code": 65, "x": 472, "y": 71, "width": 62, "height": 57, "offsetX": -8, "offsetY": -49, "advance": 45.7647 },
|
|
42
|
+
{ "code": 66, "x": 535, "y": 71, "width": 56, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 45.7647 },
|
|
43
|
+
{ "code": 67, "x": 592, "y": 71, "width": 58, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 45.7647 },
|
|
44
|
+
{ "code": 68, "x": 651, "y": 71, "width": 56, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 45.7647 },
|
|
45
|
+
{ "code": 69, "x": 708, "y": 71, "width": 52, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 42.4706 },
|
|
46
|
+
{ "code": 70, "x": 761, "y": 71, "width": 50, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 39.2353 },
|
|
47
|
+
{ "code": 71, "x": 812, "y": 71, "width": 60, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 49 },
|
|
48
|
+
{ "code": 72, "x": 873, "y": 71, "width": 57, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 49 },
|
|
49
|
+
{ "code": 73, "x": 931, "y": 71, "width": 30, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 22.8824 },
|
|
50
|
+
{ "code": 74, "x": 962, "y": 71, "width": 50, "height": 58, "offsetX": -7, "offsetY": -49, "advance": 39.2353 },
|
|
51
|
+
{ "code": 75, "x": 0, "y": 139, "width": 61, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 49 },
|
|
52
|
+
{ "code": 76, "x": 62, "y": 139, "width": 51, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 39.2353 },
|
|
53
|
+
{ "code": 77, "x": 114, "y": 139, "width": 65, "height": 57, "offsetX": -5, "offsetY": -49, "advance": 55.5294 },
|
|
54
|
+
{ "code": 78, "x": 180, "y": 139, "width": 57, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 49 },
|
|
55
|
+
{ "code": 79, "x": 238, "y": 139, "width": 61, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 49 },
|
|
56
|
+
{ "code": 80, "x": 300, "y": 139, "width": 52, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 42.4706 },
|
|
57
|
+
{ "code": 81, "x": 353, "y": 139, "width": 61, "height": 66, "offsetX": -6, "offsetY": -50, "advance": 49 },
|
|
58
|
+
{ "code": 82, "x": 415, "y": 139, "width": 56, "height": 57, "offsetX": -4, "offsetY": -49, "advance": 45.7647 },
|
|
59
|
+
{ "code": 83, "x": 472, "y": 139, "width": 55, "height": 59, "offsetX": -6, "offsetY": -50, "advance": 42.4706 },
|
|
60
|
+
{ "code": 84, "x": 528, "y": 139, "width": 56, "height": 57, "offsetX": -7, "offsetY": -49, "advance": 42.4706 },
|
|
61
|
+
{ "code": 85, "x": 585, "y": 139, "width": 57, "height": 58, "offsetX": -4, "offsetY": -49, "advance": 49 },
|
|
62
|
+
{ "code": 86, "x": 643, "y": 139, "width": 60, "height": 57, "offsetX": -7, "offsetY": -49, "advance": 45.7647 },
|
|
63
|
+
{ "code": 87, "x": 704, "y": 139, "width": 75, "height": 57, "offsetX": -8, "offsetY": -49, "advance": 58.8235 },
|
|
64
|
+
{ "code": 88, "x": 780, "y": 139, "width": 62, "height": 57, "offsetX": -8, "offsetY": -49, "advance": 45.7647 },
|
|
65
|
+
{ "code": 89, "x": 843, "y": 139, "width": 62, "height": 57, "offsetX": -8, "offsetY": -49, "advance": 45.7647 },
|
|
66
|
+
{ "code": 90, "x": 906, "y": 139, "width": 56, "height": 57, "offsetX": -7, "offsetY": -49, "advance": 42.4706 },
|
|
67
|
+
{ "code": 91, "x": 963, "y": 139, "width": 33, "height": 70, "offsetX": -5, "offsetY": -52, "advance": 22.8824 },
|
|
68
|
+
{ "code": 92, "x": 0, "y": 210, "width": 34, "height": 63, "offsetX": -9, "offsetY": -52, "advance": 16.3529 },
|
|
69
|
+
{ "code": 93, "x": 35, "y": 210, "width": 33, "height": 70, "offsetX": -5, "offsetY": -52, "advance": 22.8824 },
|
|
70
|
+
{ "code": 94, "x": 69, "y": 210, "width": 49, "height": 38, "offsetX": -5, "offsetY": -49, "advance": 38.8235 },
|
|
71
|
+
{ "code": 95, "x": 119, "y": 210, "width": 46, "height": 21, "offsetX": -8, "offsetY": -5, "advance": 29.4118 },
|
|
72
|
+
{ "code": 96, "x": 166, "y": 210, "width": 34, "height": 25, "offsetX": -7, "offsetY": -51, "advance": 19.5882 },
|
|
73
|
+
{ "code": 97, "x": 201, "y": 210, "width": 54, "height": 49, "offsetX": -7, "offsetY": -40, "advance": 39.2353 },
|
|
74
|
+
{ "code": 98, "x": 256, "y": 210, "width": 51, "height": 60, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
75
|
+
{ "code": 99, "x": 308, "y": 210, "width": 51, "height": 49, "offsetX": -6, "offsetY": -40, "advance": 39.2353 },
|
|
76
|
+
{ "code": 100, "x": 360, "y": 210, "width": 50, "height": 60, "offsetX": -6, "offsetY": -51, "advance": 39.2353 },
|
|
77
|
+
{ "code": 101, "x": 411, "y": 210, "width": 52, "height": 49, "offsetX": -6, "offsetY": -40, "advance": 39.2353 },
|
|
78
|
+
{ "code": 102, "x": 464, "y": 210, "width": 39, "height": 60, "offsetX": -8, "offsetY": -52, "advance": 22.8824 },
|
|
79
|
+
{ "code": 103, "x": 504, "y": 210, "width": 55, "height": 66, "offsetX": -8, "offsetY": -45, "advance": 39.2353 },
|
|
80
|
+
{ "code": 104, "x": 560, "y": 210, "width": 49, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
81
|
+
{ "code": 105, "x": 610, "y": 210, "width": 29, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 19.5882 },
|
|
82
|
+
{ "code": 106, "x": 640, "y": 210, "width": 35, "height": 72, "offsetX": -11, "offsetY": -51, "advance": 19.5882 },
|
|
83
|
+
{ "code": 107, "x": 676, "y": 210, "width": 53, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
84
|
+
{ "code": 108, "x": 730, "y": 210, "width": 29, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 19.5882 },
|
|
85
|
+
{ "code": 109, "x": 760, "y": 210, "width": 69, "height": 48, "offsetX": -5, "offsetY": -40, "advance": 58.8235 },
|
|
86
|
+
{ "code": 110, "x": 830, "y": 210, "width": 49, "height": 48, "offsetX": -5, "offsetY": -40, "advance": 39.2353 },
|
|
87
|
+
{ "code": 111, "x": 880, "y": 210, "width": 52, "height": 49, "offsetX": -6, "offsetY": -40, "advance": 39.2353 },
|
|
88
|
+
{ "code": 112, "x": 933, "y": 210, "width": 51, "height": 60, "offsetX": -5, "offsetY": -40, "advance": 39.2353 },
|
|
89
|
+
{ "code": 113, "x": 0, "y": 283, "width": 50, "height": 60, "offsetX": -6, "offsetY": -40, "advance": 39.2353 },
|
|
90
|
+
{ "code": 114, "x": 51, "y": 283, "width": 39, "height": 48, "offsetX": -5, "offsetY": -40, "advance": 26.1176 },
|
|
91
|
+
{ "code": 115, "x": 91, "y": 283, "width": 49, "height": 49, "offsetX": -7, "offsetY": -40, "advance": 35.9412 },
|
|
92
|
+
{ "code": 116, "x": 141, "y": 283, "width": 40, "height": 58, "offsetX": -7, "offsetY": -49, "advance": 26.1176 },
|
|
93
|
+
{ "code": 117, "x": 182, "y": 283, "width": 49, "height": 49, "offsetX": -5, "offsetY": -40, "advance": 39.2353 },
|
|
94
|
+
{ "code": 118, "x": 232, "y": 283, "width": 52, "height": 48, "offsetX": -8, "offsetY": -40, "advance": 35.9412 },
|
|
95
|
+
{ "code": 119, "x": 285, "y": 283, "width": 72, "height": 48, "offsetX": -8, "offsetY": -40, "advance": 55.5294 },
|
|
96
|
+
{ "code": 120, "x": 358, "y": 283, "width": 55, "height": 48, "offsetX": -8, "offsetY": -40, "advance": 39.2353 },
|
|
97
|
+
{ "code": 121, "x": 414, "y": 283, "width": 52, "height": 61, "offsetX": -8, "offsetY": -40, "advance": 35.9412 },
|
|
98
|
+
{ "code": 122, "x": 467, "y": 283, "width": 47, "height": 48, "offsetX": -7, "offsetY": -40, "advance": 32.7059 },
|
|
99
|
+
{ "code": 123, "x": 515, "y": 283, "width": 36, "height": 70, "offsetX": -7, "offsetY": -52, "advance": 22.8824 },
|
|
100
|
+
{ "code": 124, "x": 552, "y": 283, "width": 24, "height": 72, "offsetX": -4, "offsetY": -51, "advance": 16.3529 },
|
|
101
|
+
{ "code": 125, "x": 577, "y": 283, "width": 36, "height": 70, "offsetX": -6, "offsetY": -52, "advance": 22.8824 },
|
|
102
|
+
{ "code": 126, "x": 614, "y": 283, "width": 51, "height": 30, "offsetX": -6, "offsetY": -33, "advance": 38.8235 },
|
|
103
|
+
{ "code": 225, "x": 666, "y": 283, "width": 54, "height": 60, "offsetX": -7, "offsetY": -51, "advance": 39.2353 },
|
|
104
|
+
{ "code": 233, "x": 721, "y": 283, "width": 52, "height": 60, "offsetX": -6, "offsetY": -51, "advance": 39.2353 },
|
|
105
|
+
{ "code": 237, "x": 774, "y": 283, "width": 34, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 19.5882 },
|
|
106
|
+
{ "code": 243, "x": 809, "y": 283, "width": 52, "height": 60, "offsetX": -6, "offsetY": -51, "advance": 39.2353 },
|
|
107
|
+
{ "code": 250, "x": 862, "y": 283, "width": 49, "height": 60, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
108
|
+
{ "code": 193, "x": 912, "y": 283, "width": 62, "height": 69, "offsetX": -8, "offsetY": -61, "advance": 45.7647 },
|
|
109
|
+
{ "code": 201, "x": 0, "y": 356, "width": 52, "height": 69, "offsetX": -4, "offsetY": -61, "advance": 42.4706 },
|
|
110
|
+
{ "code": 205, "x": 53, "y": 356, "width": 35, "height": 69, "offsetX": -4, "offsetY": -61, "advance": 22.8824 },
|
|
111
|
+
{ "code": 211, "x": 89, "y": 356, "width": 61, "height": 70, "offsetX": -6, "offsetY": -61, "advance": 49 },
|
|
112
|
+
{ "code": 218, "x": 151, "y": 356, "width": 57, "height": 70, "offsetX": -4, "offsetY": -61, "advance": 49 },
|
|
113
|
+
{ "code": 241, "x": 209, "y": 356, "width": 49, "height": 59, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
114
|
+
{ "code": 209, "x": 259, "y": 356, "width": 57, "height": 69, "offsetX": -4, "offsetY": -61, "advance": 49 },
|
|
115
|
+
{ "code": 161, "x": 317, "y": 356, "width": 31, "height": 58, "offsetX": -6, "offsetY": -40, "advance": 19.5882 },
|
|
116
|
+
{ "code": 191, "x": 349, "y": 356, "width": 48, "height": 58, "offsetX": -6, "offsetY": -39, "advance": 35.9412 },
|
|
117
|
+
{ "code": 252, "x": 398, "y": 356, "width": 49, "height": 60, "offsetX": -5, "offsetY": -51, "advance": 39.2353 },
|
|
118
|
+
{ "code": 220, "x": 448, "y": 356, "width": 57, "height": 70, "offsetX": -4, "offsetY": -61, "advance": 49 },
|
|
119
|
+
{ "code": 176, "x": 506, "y": 356, "width": 35, "height": 35, "offsetX": -6, "offsetY": -50, "advance": 23.5294 },
|
|
120
|
+
{ "code": 8230, "x": 542, "y": 356, "width": 69, "height": 28, "offsetX": -5, "offsetY": -20, "advance": 58.8235 },
|
|
121
|
+
{ "code": 215, "x": 612, "y": 356, "width": 47, "height": 47, "offsetX": -4, "offsetY": -39, "advance": 38.8235 },
|
|
122
|
+
{ "code": 183, "x": 660, "y": 356, "width": 28, "height": 28, "offsetX": -4, "offsetY": -33, "advance": 19.5882 },
|
|
123
|
+
{ "code": 8211, "x": 689, "y": 356, "width": 46, "height": 25, "offsetX": -8, "offsetY": -28, "advance": 29.4118 },
|
|
124
|
+
{ "code": 8212, "x": 736, "y": 356, "width": 75, "height": 25, "offsetX": -8, "offsetY": -28, "advance": 58.8235 },
|
|
125
|
+
{ "code": 8216, "x": 812, "y": 356, "width": 28, "height": 38, "offsetX": -6, "offsetY": -49, "advance": 16.3529 },
|
|
126
|
+
{ "code": 8217, "x": 841, "y": 356, "width": 28, "height": 38, "offsetX": -6, "offsetY": -49, "advance": 16.3529 },
|
|
127
|
+
{ "code": 8220, "x": 870, "y": 356, "width": 41, "height": 38, "offsetX": -6, "offsetY": -49, "advance": 29.4118 },
|
|
128
|
+
{ "code": 8221, "x": 912, "y": 356, "width": 41, "height": 38, "offsetX": -6, "offsetY": -49, "advance": 29.4118 },
|
|
129
|
+
{ "code": 8226, "x": 954, "y": 356, "width": 36, "height": 36, "offsetX": -3, "offsetY": -38, "advance": 29.4118 }
|
|
130
|
+
],
|
|
131
|
+
"atlasWidth": 1024,
|
|
132
|
+
"atlasHeight": 426
|
|
133
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"size": 64,
|
|
3
|
+
"ascent": 53.1128,
|
|
4
|
+
"descent": -10.8872,
|
|
5
|
+
"lineGap": 0,
|
|
6
|
+
"distanceRange": 8,
|
|
7
|
+
"glyphs": [
|
|
8
|
+
{ "code": 32, "x": 0, "y": 0, "width": 0, "height": 0, "offsetX": 0, "offsetY": 0, "advance": 11.7293 },
|
|
9
|
+
{ "code": 33, "x": 1, "y": 0, "width": 41, "height": 63, "offsetX": -9, "offsetY": -53, "advance": 15.8195 },
|
|
10
|
+
{ "code": 34, "x": 43, "y": 0, "width": 47, "height": 38, "offsetX": -4, "offsetY": -56, "advance": 25.3233 },
|
|
11
|
+
{ "code": 35, "x": 91, "y": 0, "width": 56, "height": 62, "offsetX": -6, "offsetY": -53, "advance": 37.6541 },
|
|
12
|
+
{ "code": 36, "x": 148, "y": 0, "width": 46, "height": 70, "offsetX": -7, "offsetY": -57, "advance": 26.5865 },
|
|
13
|
+
{ "code": 37, "x": 195, "y": 0, "width": 62, "height": 65, "offsetX": -4, "offsetY": -55, "advance": 48.0602 },
|
|
14
|
+
{ "code": 38, "x": 258, "y": 0, "width": 52, "height": 62, "offsetX": -7, "offsetY": -53, "advance": 32.1805 },
|
|
15
|
+
{ "code": 39, "x": 311, "y": 0, "width": 33, "height": 37, "offsetX": -4, "offsetY": -56, "advance": 12.1504 },
|
|
16
|
+
{ "code": 40, "x": 345, "y": 0, "width": 43, "height": 78, "offsetX": -6, "offsetY": -61, "advance": 20.0902 },
|
|
17
|
+
{ "code": 41, "x": 389, "y": 0, "width": 44, "height": 78, "offsetX": -12, "offsetY": -61, "advance": 19.9098 },
|
|
18
|
+
{ "code": 42, "x": 434, "y": 0, "width": 41, "height": 44, "offsetX": -4, "offsetY": -49, "advance": 25.4436 },
|
|
19
|
+
{ "code": 43, "x": 476, "y": 0, "width": 35, "height": 41, "offsetX": -5, "offsetY": -39, "advance": 19.8496 },
|
|
20
|
+
{ "code": 44, "x": 512, "y": 0, "width": 32, "height": 34, "offsetX": -11, "offsetY": -19, "advance": 11.8496 },
|
|
21
|
+
{ "code": 45, "x": 545, "y": 0, "width": 38, "height": 28, "offsetX": -6, "offsetY": -34, "advance": 21.6541 },
|
|
22
|
+
{ "code": 46, "x": 584, "y": 0, "width": 30, "height": 29, "offsetX": -9, "offsetY": -19, "advance": 12.5714 },
|
|
23
|
+
{ "code": 47, "x": 615, "y": 0, "width": 57, "height": 64, "offsetX": -9, "offsetY": -54, "advance": 33.1429 },
|
|
24
|
+
{ "code": 48, "x": 673, "y": 0, "width": 46, "height": 61, "offsetX": -6, "offsetY": -53, "advance": 28.5113 },
|
|
25
|
+
{ "code": 49, "x": 720, "y": 0, "width": 36, "height": 60, "offsetX": -4, "offsetY": -52, "advance": 19.2481 },
|
|
26
|
+
{ "code": 50, "x": 757, "y": 0, "width": 49, "height": 60, "offsetX": -8, "offsetY": -52, "advance": 28.0902 },
|
|
27
|
+
{ "code": 51, "x": 807, "y": 0, "width": 48, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 27.5489 },
|
|
28
|
+
{ "code": 52, "x": 856, "y": 0, "width": 46, "height": 61, "offsetX": -7, "offsetY": -52, "advance": 27.609 },
|
|
29
|
+
{ "code": 53, "x": 903, "y": 0, "width": 46, "height": 61, "offsetX": -6, "offsetY": -52, "advance": 27.0677 },
|
|
30
|
+
{ "code": 54, "x": 950, "y": 0, "width": 45, "height": 61, "offsetX": -5, "offsetY": -53, "advance": 28.7519 },
|
|
31
|
+
{ "code": 55, "x": 0, "y": 79, "width": 43, "height": 61, "offsetX": -7, "offsetY": -53, "advance": 20.391 },
|
|
32
|
+
{ "code": 56, "x": 44, "y": 79, "width": 51, "height": 62, "offsetX": -7, "offsetY": -53, "advance": 31.1579 },
|
|
33
|
+
{ "code": 57, "x": 96, "y": 79, "width": 45, "height": 61, "offsetX": -4, "offsetY": -53, "advance": 29.4135 },
|
|
34
|
+
{ "code": 58, "x": 142, "y": 79, "width": 31, "height": 49, "offsetX": -7, "offsetY": -43, "advance": 11.7293 },
|
|
35
|
+
{ "code": 59, "x": 174, "y": 79, "width": 33, "height": 52, "offsetX": -7, "offsetY": -42, "advance": 15.7594 },
|
|
36
|
+
{ "code": 60, "x": 208, "y": 79, "width": 37, "height": 60, "offsetX": -6, "offsetY": -53, "advance": 19.0075 },
|
|
37
|
+
{ "code": 61, "x": 246, "y": 79, "width": 37, "height": 42, "offsetX": -6, "offsetY": -40, "advance": 21.0526 },
|
|
38
|
+
{ "code": 62, "x": 284, "y": 79, "width": 37, "height": 60, "offsetX": -8, "offsetY": -52, "advance": 17.3233 },
|
|
39
|
+
{ "code": 63, "x": 322, "y": 79, "width": 42, "height": 62, "offsetX": -3, "offsetY": -52, "advance": 26.3459 },
|
|
40
|
+
{ "code": 64, "x": 365, "y": 79, "width": 76, "height": 62, "offsetX": -6, "offsetY": -52, "advance": 59.3684 },
|
|
41
|
+
{ "code": 65, "x": 442, "y": 79, "width": 46, "height": 61, "offsetX": -10, "offsetY": -53, "advance": 26.0451 },
|
|
42
|
+
{ "code": 66, "x": 489, "y": 79, "width": 48, "height": 62, "offsetX": -9, "offsetY": -53, "advance": 25.7444 },
|
|
43
|
+
{ "code": 67, "x": 538, "y": 79, "width": 45, "height": 61, "offsetX": -6, "offsetY": -52, "advance": 24.9023 },
|
|
44
|
+
{ "code": 68, "x": 584, "y": 79, "width": 47, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 26.5263 },
|
|
45
|
+
{ "code": 69, "x": 632, "y": 79, "width": 44, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 20.5113 },
|
|
46
|
+
{ "code": 70, "x": 677, "y": 79, "width": 45, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 20.5113 },
|
|
47
|
+
{ "code": 71, "x": 723, "y": 79, "width": 49, "height": 63, "offsetX": -6, "offsetY": -53, "advance": 30.015 },
|
|
48
|
+
{ "code": 72, "x": 773, "y": 79, "width": 49, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 26.5865 },
|
|
49
|
+
{ "code": 73, "x": 823, "y": 79, "width": 35, "height": 60, "offsetX": -9, "offsetY": -52, "advance": 12.0301 },
|
|
50
|
+
{ "code": 74, "x": 859, "y": 79, "width": 43, "height": 64, "offsetX": -10, "offsetY": -55, "advance": 16.782 },
|
|
51
|
+
{ "code": 75, "x": 903, "y": 79, "width": 53, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 27.7895 },
|
|
52
|
+
{ "code": 76, "x": 957, "y": 79, "width": 35, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 18.1654 },
|
|
53
|
+
{ "code": 77, "x": 0, "y": 144, "width": 60, "height": 62, "offsetX": -10, "offsetY": -54, "advance": 37.2331 },
|
|
54
|
+
{ "code": 78, "x": 61, "y": 144, "width": 50, "height": 63, "offsetX": -8, "offsetY": -54, "advance": 27.2481 },
|
|
55
|
+
{ "code": 79, "x": 112, "y": 144, "width": 49, "height": 62, "offsetX": -7, "offsetY": -53, "advance": 29.6541 },
|
|
56
|
+
{ "code": 80, "x": 162, "y": 144, "width": 46, "height": 62, "offsetX": -9, "offsetY": -53, "advance": 22.797 },
|
|
57
|
+
{ "code": 81, "x": 209, "y": 144, "width": 50, "height": 64, "offsetX": -7, "offsetY": -53, "advance": 31.5789 },
|
|
58
|
+
{ "code": 82, "x": 260, "y": 144, "width": 47, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 26.7068 },
|
|
59
|
+
{ "code": 83, "x": 308, "y": 144, "width": 48, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 25.8647 },
|
|
60
|
+
{ "code": 84, "x": 357, "y": 144, "width": 40, "height": 61, "offsetX": -3, "offsetY": -52, "advance": 21.7744 },
|
|
61
|
+
{ "code": 85, "x": 398, "y": 144, "width": 47, "height": 62, "offsetX": -7, "offsetY": -52, "advance": 25.203 },
|
|
62
|
+
{ "code": 86, "x": 446, "y": 144, "width": 44, "height": 62, "offsetX": -5, "offsetY": -53, "advance": 22.797 },
|
|
63
|
+
{ "code": 87, "x": 491, "y": 144, "width": 58, "height": 62, "offsetX": -6, "offsetY": -53, "advance": 36.1504 },
|
|
64
|
+
{ "code": 88, "x": 550, "y": 144, "width": 51, "height": 61, "offsetX": -11, "offsetY": -52, "advance": 24.7218 },
|
|
65
|
+
{ "code": 89, "x": 602, "y": 144, "width": 44, "height": 61, "offsetX": -4, "offsetY": -53, "advance": 23.0977 },
|
|
66
|
+
{ "code": 90, "x": 647, "y": 144, "width": 48, "height": 59, "offsetX": -8, "offsetY": -51, "advance": 25.9248 },
|
|
67
|
+
{ "code": 91, "x": 696, "y": 144, "width": 43, "height": 64, "offsetX": -7, "offsetY": -55, "advance": 19.188 },
|
|
68
|
+
{ "code": 92, "x": 740, "y": 144, "width": 39, "height": 62, "offsetX": -1, "offsetY": -54, "advance": 30.6767 },
|
|
69
|
+
{ "code": 93, "x": 780, "y": 144, "width": 43, "height": 64, "offsetX": -10, "offsetY": -54, "advance": 18.8271 },
|
|
70
|
+
{ "code": 94, "x": 824, "y": 144, "width": 37, "height": 34, "offsetX": -2, "offsetY": -62, "advance": 24.0602 },
|
|
71
|
+
{ "code": 95, "x": 862, "y": 144, "width": 44, "height": 24, "offsetX": -12, "offsetY": -9, "advance": 24.1203 },
|
|
72
|
+
{ "code": 96, "x": 907, "y": 144, "width": 33, "height": 36, "offsetX": -6, "offsetY": -62, "advance": 21.5338 },
|
|
73
|
+
{ "code": 97, "x": 941, "y": 144, "width": 46, "height": 61, "offsetX": -10, "offsetY": -53, "advance": 26.0451 },
|
|
74
|
+
{ "code": 98, "x": 0, "y": 209, "width": 48, "height": 62, "offsetX": -9, "offsetY": -53, "advance": 25.7444 },
|
|
75
|
+
{ "code": 99, "x": 49, "y": 209, "width": 45, "height": 61, "offsetX": -6, "offsetY": -52, "advance": 24.9023 },
|
|
76
|
+
{ "code": 100, "x": 95, "y": 209, "width": 47, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 26.5263 },
|
|
77
|
+
{ "code": 101, "x": 143, "y": 209, "width": 44, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 20.5113 },
|
|
78
|
+
{ "code": 102, "x": 188, "y": 209, "width": 45, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 20.5113 },
|
|
79
|
+
{ "code": 103, "x": 234, "y": 209, "width": 49, "height": 63, "offsetX": -6, "offsetY": -53, "advance": 30.015 },
|
|
80
|
+
{ "code": 104, "x": 284, "y": 209, "width": 49, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 26.5263 },
|
|
81
|
+
{ "code": 105, "x": 334, "y": 209, "width": 34, "height": 60, "offsetX": -8, "offsetY": -52, "advance": 12.1504 },
|
|
82
|
+
{ "code": 106, "x": 369, "y": 209, "width": 43, "height": 64, "offsetX": -10, "offsetY": -55, "advance": 16.782 },
|
|
83
|
+
{ "code": 107, "x": 413, "y": 209, "width": 53, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 27.7895 },
|
|
84
|
+
{ "code": 108, "x": 467, "y": 209, "width": 35, "height": 62, "offsetX": -8, "offsetY": -53, "advance": 18.1654 },
|
|
85
|
+
{ "code": 109, "x": 503, "y": 209, "width": 60, "height": 62, "offsetX": -10, "offsetY": -54, "advance": 37.2331 },
|
|
86
|
+
{ "code": 110, "x": 564, "y": 209, "width": 50, "height": 63, "offsetX": -8, "offsetY": -54, "advance": 27.188 },
|
|
87
|
+
{ "code": 111, "x": 615, "y": 209, "width": 49, "height": 62, "offsetX": -7, "offsetY": -53, "advance": 29.6541 },
|
|
88
|
+
{ "code": 112, "x": 665, "y": 209, "width": 46, "height": 62, "offsetX": -9, "offsetY": -53, "advance": 22.797 },
|
|
89
|
+
{ "code": 113, "x": 712, "y": 209, "width": 50, "height": 64, "offsetX": -7, "offsetY": -53, "advance": 31.5789 },
|
|
90
|
+
{ "code": 114, "x": 763, "y": 209, "width": 47, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 26.7068 },
|
|
91
|
+
{ "code": 115, "x": 811, "y": 209, "width": 48, "height": 61, "offsetX": -8, "offsetY": -52, "advance": 25.8647 },
|
|
92
|
+
{ "code": 116, "x": 860, "y": 209, "width": 40, "height": 61, "offsetX": -3, "offsetY": -52, "advance": 21.7744 },
|
|
93
|
+
{ "code": 117, "x": 901, "y": 209, "width": 47, "height": 62, "offsetX": -7, "offsetY": -52, "advance": 25.203 },
|
|
94
|
+
{ "code": 118, "x": 949, "y": 209, "width": 44, "height": 62, "offsetX": -5, "offsetY": -53, "advance": 22.797 },
|
|
95
|
+
{ "code": 119, "x": 0, "y": 274, "width": 58, "height": 62, "offsetX": -6, "offsetY": -53, "advance": 36.1504 },
|
|
96
|
+
{ "code": 120, "x": 59, "y": 274, "width": 51, "height": 61, "offsetX": -11, "offsetY": -52, "advance": 24.7218 },
|
|
97
|
+
{ "code": 121, "x": 111, "y": 274, "width": 44, "height": 61, "offsetX": -4, "offsetY": -53, "advance": 23.0977 },
|
|
98
|
+
{ "code": 122, "x": 156, "y": 274, "width": 48, "height": 59, "offsetX": -8, "offsetY": -51, "advance": 25.9248 },
|
|
99
|
+
{ "code": 123, "x": 205, "y": 274, "width": 36, "height": 60, "offsetX": -6, "offsetY": -52, "advance": 16.9023 },
|
|
100
|
+
{ "code": 124, "x": 242, "y": 274, "width": 35, "height": 64, "offsetX": -7, "offsetY": -54, "advance": 14.9173 },
|
|
101
|
+
{ "code": 125, "x": 278, "y": 274, "width": 36, "height": 60, "offsetX": -7, "offsetY": -52, "advance": 16.4211 },
|
|
102
|
+
{ "code": 126, "x": 315, "y": 274, "width": 43, "height": 31, "offsetX": -6, "offsetY": -37, "advance": 25.3233 },
|
|
103
|
+
{ "code": 225, "x": 359, "y": 274, "width": 46, "height": 83, "offsetX": -10, "offsetY": -75, "advance": 26.0451 },
|
|
104
|
+
{ "code": 233, "x": 406, "y": 274, "width": 44, "height": 83, "offsetX": -8, "offsetY": -74, "advance": 20.5113 },
|
|
105
|
+
{ "code": 237, "x": 451, "y": 274, "width": 38, "height": 81, "offsetX": -8, "offsetY": -73, "advance": 12.1504 },
|
|
106
|
+
{ "code": 243, "x": 490, "y": 274, "width": 49, "height": 84, "offsetX": -7, "offsetY": -75, "advance": 29.6541 },
|
|
107
|
+
{ "code": 250, "x": 540, "y": 274, "width": 47, "height": 83, "offsetX": -7, "offsetY": -73, "advance": 25.203 },
|
|
108
|
+
{ "code": 193, "x": 588, "y": 274, "width": 47, "height": 82, "offsetX": -10, "offsetY": -74, "advance": 26.0451 },
|
|
109
|
+
{ "code": 201, "x": 636, "y": 274, "width": 44, "height": 85, "offsetX": -8, "offsetY": -76, "advance": 20.5113 },
|
|
110
|
+
{ "code": 205, "x": 681, "y": 274, "width": 42, "height": 82, "offsetX": -9, "offsetY": -74, "advance": 12.0301 },
|
|
111
|
+
{ "code": 211, "x": 724, "y": 274, "width": 51, "height": 85, "offsetX": -7, "offsetY": -76, "advance": 29.6541 },
|
|
112
|
+
{ "code": 218, "x": 776, "y": 274, "width": 48, "height": 85, "offsetX": -7, "offsetY": -75, "advance": 25.203 },
|
|
113
|
+
{ "code": 241, "x": 825, "y": 274, "width": 55, "height": 77, "offsetX": -8, "offsetY": -68, "advance": 27.188 },
|
|
114
|
+
{ "code": 209, "x": 881, "y": 274, "width": 55, "height": 77, "offsetX": -8, "offsetY": -68, "advance": 27.2481 },
|
|
115
|
+
{ "code": 161, "x": 937, "y": 274, "width": 41, "height": 63, "offsetX": -13, "offsetY": -55, "advance": 15.8797 },
|
|
116
|
+
{ "code": 191, "x": 979, "y": 274, "width": 42, "height": 63, "offsetX": -7, "offsetY": -53, "advance": 26.406 },
|
|
117
|
+
{ "code": 252, "x": 0, "y": 360, "width": 51, "height": 78, "offsetX": -7, "offsetY": -68, "advance": 25.203 },
|
|
118
|
+
{ "code": 220, "x": 52, "y": 360, "width": 51, "height": 79, "offsetX": -7, "offsetY": -69, "advance": 25.203 },
|
|
119
|
+
{ "code": 176, "x": 104, "y": 360, "width": 32, "height": 31, "offsetX": -3, "offsetY": -53, "advance": 14.3158 },
|
|
120
|
+
{ "code": 8230, "x": 137, "y": 360, "width": 62, "height": 29, "offsetX": -8, "offsetY": -19, "advance": 46.3759 },
|
|
121
|
+
{ "code": 215, "x": 200, "y": 360, "width": 47, "height": 45, "offsetX": -7, "offsetY": -43, "advance": 28.5714 },
|
|
122
|
+
{ "code": 183, "x": 248, "y": 360, "width": 30, "height": 29, "offsetX": -6, "offsetY": -37, "advance": 12.8722 },
|
|
123
|
+
{ "code": 8211, "x": 279, "y": 360, "width": 38, "height": 28, "offsetX": -6, "offsetY": -34, "advance": 21.6541 },
|
|
124
|
+
{ "code": 8212, "x": 318, "y": 360, "width": 48, "height": 28, "offsetX": -6, "offsetY": -34, "advance": 31.2782 },
|
|
125
|
+
{ "code": 8216, "x": 367, "y": 360, "width": 33, "height": 37, "offsetX": -4, "offsetY": -56, "advance": 12.1504 },
|
|
126
|
+
{ "code": 8217, "x": 401, "y": 360, "width": 33, "height": 36, "offsetX": -4, "offsetY": -56, "advance": 12.1504 },
|
|
127
|
+
{ "code": 8220, "x": 435, "y": 360, "width": 43, "height": 41, "offsetX": -4, "offsetY": -54, "advance": 24.2406 },
|
|
128
|
+
{ "code": 8221, "x": 479, "y": 360, "width": 43, "height": 41, "offsetX": -4, "offsetY": -56, "advance": 24.0602 },
|
|
129
|
+
{ "code": 8226, "x": 523, "y": 360, "width": 35, "height": 38, "offsetX": -6, "offsetY": -41, "advance": 17.0827 }
|
|
130
|
+
],
|
|
131
|
+
"atlasWidth": 1024,
|
|
132
|
+
"atlasHeight": 439
|
|
133
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CONFIG_FILE, CliError, type Ctx, loadConfig, print } from "../lib";
|
|
4
|
+
|
|
5
|
+
// Dotted keys: targets.web.deploy.project
|
|
6
|
+
export async function configCmd(ctx: Ctx, args: string[]): Promise<void> {
|
|
7
|
+
const [op, key, value] = args;
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const file = join(config.root, CONFIG_FILE);
|
|
10
|
+
const raw = JSON.parse(readFileSync(file, "utf8")) as Record<string, unknown>;
|
|
11
|
+
const parts = key ? key.split(".") : [];
|
|
12
|
+
if (op === "get" || op === undefined) {
|
|
13
|
+
let cur: unknown = raw;
|
|
14
|
+
for (const p of parts) cur = (cur as Record<string, unknown> | undefined)?.[p];
|
|
15
|
+
if (cur === undefined) throw new CliError("UNKNOWN_KEY", `${key} is not set`, "dotframe config get");
|
|
16
|
+
print(ctx, { key: key ?? null, value: cur }, (): string => (typeof cur === "string" ? cur : JSON.stringify(cur, null, 2)));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (op !== "set" || !key || value === undefined) throw new CliError("MISSING_ARG", "config set needs <key> <json value>", `dotframe config set targets.ios.device '"00008120-..."'`);
|
|
20
|
+
let parsed: unknown;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(value);
|
|
23
|
+
} catch {
|
|
24
|
+
parsed = value;
|
|
25
|
+
}
|
|
26
|
+
let cur = raw;
|
|
27
|
+
for (const p of parts.slice(0, -1)) cur = (cur[p] ??= {}) as Record<string, unknown>;
|
|
28
|
+
cur[parts[parts.length - 1]] = parsed;
|
|
29
|
+
if (ctx.dryRun) {
|
|
30
|
+
print(ctx, { dryRun: true, key, value: parsed }, (): string => `would set ${key} = ${JSON.stringify(parsed)}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
writeFileSync(file, `${JSON.stringify(raw, null, 2)}\n`);
|
|
34
|
+
print(ctx, { key, value: parsed }, (): string => `${key} = ${JSON.stringify(parsed)}`);
|
|
35
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readlinkSync, symlinkSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { type Config, type Ctx, findRoot, home, loadConfig, print, which } from "../lib";
|
|
4
|
+
import { loadSim } from "../simkit";
|
|
5
|
+
|
|
6
|
+
interface Check {
|
|
7
|
+
check: string;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
detail: string;
|
|
10
|
+
fix: string;
|
|
11
|
+
skill: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const tool = (bin: string, why: string, fix: string, skill: string): Check => {
|
|
15
|
+
const path = which(bin);
|
|
16
|
+
return { check: `tool:${bin}`, ok: path !== null, detail: path ?? `missing (${why})`, fix: path ? "" : fix, skill };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function doctor(ctx: Ctx, fix: boolean): Promise<void> {
|
|
20
|
+
const checks: Check[] = [tool("bun", "runs the CLI and the web build", "curl -fsSL https://bun.sh/install | bash", "core")];
|
|
21
|
+
let config: Config | null = null;
|
|
22
|
+
if (!findRoot()) checks.push({ check: "config", ok: false, detail: "no dotframe.json here or above", fix: "cd into a game repo or dotframe new <name>", skill: "core" });
|
|
23
|
+
else {
|
|
24
|
+
try {
|
|
25
|
+
config = loadConfig();
|
|
26
|
+
checks.push({ check: "config", ok: true, detail: `${config.root}/dotframe.json`, fix: "", skill: "core" });
|
|
27
|
+
} catch (error) {
|
|
28
|
+
checks.push({ check: "config", ok: false, detail: error instanceof Error ? error.message : "unreadable", fix: "fix the JSON", skill: "core" });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (config) {
|
|
32
|
+
if (config.sim) {
|
|
33
|
+
try {
|
|
34
|
+
const sim = await loadSim(config);
|
|
35
|
+
checks.push({ check: "sim", ok: true, detail: `${config.sim} (${sim.players} players)`, fix: "", skill: "core" });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
checks.push({ check: "sim", ok: false, detail: error instanceof Error ? error.message : "failed to load", fix: "dotframe skills get core (Sim contract)", skill: "core" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const targets = Object.keys(config.targets);
|
|
41
|
+
if (targets.some((t: string): boolean => t === "web" || t === "discord")) {
|
|
42
|
+
checks.push(tool("vercel", "deploy web", "npm i -g vercel", "export-web"), tool("agent-browser", "dotframe snap", "npm i -g agent-browser && agent-browser install", "core"));
|
|
43
|
+
}
|
|
44
|
+
if (targets.includes("ios") || targets.includes("macos")) checks.push(tool("scriptc", "native builds", "npm i -g scriptc", "macos"));
|
|
45
|
+
if (targets.includes("ios")) checks.push(tool("xcodegen", "iOS project", "brew install xcodegen", "ios"), tool("xcodebuild", "iOS build", "install Xcode", "ios"));
|
|
46
|
+
if (config.relay) checks.push(config.relay.provider === "fly" ? tool("fly", "relay deploy", "brew install flyctl && fly auth login", "relay") : tool("vps", "relay deploy", "install the vps CLI and log in to the crafter profile", "relay"));
|
|
47
|
+
for (const link of config.links ?? []) {
|
|
48
|
+
const path = resolve(config.root, link.path);
|
|
49
|
+
const want = home(link.target);
|
|
50
|
+
let isLink = false;
|
|
51
|
+
try {
|
|
52
|
+
isLink = lstatSync(path).isSymbolicLink();
|
|
53
|
+
} catch {}
|
|
54
|
+
let ok = existsSync(path) && existsSync(want);
|
|
55
|
+
let detail = ok ? `${link.path} -> ${isLink ? readlinkSync(path) : "(directory)"}` : `${link.path} missing or broken`;
|
|
56
|
+
if (!ok && fix && existsSync(want)) {
|
|
57
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
58
|
+
if (isLink) unlinkSync(path);
|
|
59
|
+
symlinkSync(want, path);
|
|
60
|
+
ok = true;
|
|
61
|
+
detail = `${link.path} -> ${want} (created)`;
|
|
62
|
+
}
|
|
63
|
+
checks.push({ check: `link:${link.path}`, ok, detail, fix: ok ? "" : existsSync(want) ? "dotframe doctor --fix" : `${want} does not exist: run dotframe's scripts/vendor.sh first`, skill: "ios" });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const failed = checks.filter((c: Check): boolean => !c.ok);
|
|
67
|
+
print(ctx, { ok: failed.length === 0, checks }, (): string =>
|
|
68
|
+
checks.map((c: Check): string => `${c.ok ? "ok " : "FAIL"} ${c.check}: ${c.detail}${c.fix ? `\n fix: ${c.fix}` : ""}`).join("\n"),
|
|
69
|
+
);
|
|
70
|
+
if (failed.length > 0) process.exit(1);
|
|
71
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { CliError, type Ctx, exec, loadConfig, print, runSteps, target } from "../lib";
|
|
4
|
+
|
|
5
|
+
const TEMPLATES = resolve(import.meta.dir, "../../templates");
|
|
6
|
+
const STUB = `---
|
|
7
|
+
name: dotframe
|
|
8
|
+
description: Build, test, and export this dotframe game. Use when changing gameplay, simulating or replaying frames, checking a render, debugging netplay desyncs, or building and deploying a target.
|
|
9
|
+
---
|
|
10
|
+
Run \`dotframe skills get core\` before any dotframe command. It matches the installed CLI version.
|
|
11
|
+
List the specialized guides with \`dotframe skills list\`.
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export async function create(ctx: Ctx, name: string | undefined, template: string, install = true): Promise<void> {
|
|
15
|
+
if (!name || !/^[a-z0-9][a-z0-9-]*$/.test(name)) throw new CliError("BAD_NAME", "new needs a lowercase name (letters, digits, dashes)", "dotframe new my-game --template fighter", "game-design");
|
|
16
|
+
const templates = readdirSync(TEMPLATES).filter((t: string): boolean => !t.startsWith("_"));
|
|
17
|
+
if (!templates.includes(template)) throw new CliError("UNKNOWN_TEMPLATE", `no template "${template}"`, `available: ${templates.join(", ")}`, "game-design");
|
|
18
|
+
const dir = resolve(process.cwd(), name);
|
|
19
|
+
if (existsSync(dir)) throw new CliError("EXISTS", `${dir} already exists`, "pick another name or delete it", "game-design");
|
|
20
|
+
const files: [string, string][] = [
|
|
21
|
+
...readdirSync(join(TEMPLATES, "_base")).map((f: string): [string, string] => [join(TEMPLATES, "_base", f), f === "gitignore" ? ".gitignore" : f]),
|
|
22
|
+
[join(TEMPLATES, template, "game.ts"), "src/game.ts"],
|
|
23
|
+
];
|
|
24
|
+
if (ctx.dryRun) {
|
|
25
|
+
print(ctx, { dryRun: true, dir, template, files: files.map(([, to]) => to) }, (): string => `would create ${dir} (${template}): ${files.map(([, to]) => to).join(", ")}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const [from, to] of files) {
|
|
29
|
+
mkdirSync(join(dir, to, ".."), { recursive: true });
|
|
30
|
+
writeFileSync(join(dir, to), readFileSync(from, "utf8").replaceAll("__NAME__", name).replaceAll("__SCOPE__", "your-vercel-team"));
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(join(dir, ".agents/skills/dotframe"), { recursive: true });
|
|
33
|
+
writeFileSync(join(dir, ".agents/skills/dotframe/SKILL.md"), STUB);
|
|
34
|
+
mkdirSync(join(dir, "replays"), { recursive: true });
|
|
35
|
+
const steps = [
|
|
36
|
+
{ label: "git init", argv: ["git", "init", "-q"] },
|
|
37
|
+
...(install ? [{ label: "install", argv: ["bun", "install"] }] : []),
|
|
38
|
+
];
|
|
39
|
+
for (const step of steps) {
|
|
40
|
+
const r = await exec(ctx, dir, step);
|
|
41
|
+
if (r.code !== 0) throw new CliError("STEP_FAILED", `${step.label}: ${r.tail}`, "on a machine with a private registry, run bun install --registry https://registry.npmjs.org", "game-design");
|
|
42
|
+
}
|
|
43
|
+
print(ctx, { dir, template, next: [`cd ${name}`, "dotframe sim --mash 7 --json", "dotframe dev"] }, (): string => `created ${dir} (${template})\nnext: cd ${name} && dotframe sim --mash 7 && dotframe dev`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Builds the web target, serves it, and rebuilds when source changes. For humans; agents use sim and snap.
|
|
47
|
+
export async function dev(ctx: Ctx, port: number): Promise<void> {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
const t = target(config, "web");
|
|
50
|
+
if (!t.out) throw new CliError("NO_OUT", "the web target needs an out dir", "", "export-web");
|
|
51
|
+
const out = resolve(config.root, t.out);
|
|
52
|
+
await runSteps(ctx, config.root, t.steps, "export-web");
|
|
53
|
+
const server = Bun.serve({
|
|
54
|
+
port,
|
|
55
|
+
fetch: (req: Request): Response => {
|
|
56
|
+
const path = decodeURIComponent(new URL(req.url).pathname);
|
|
57
|
+
return new Response(Bun.file(join(out, path === "/" ? "index.html" : path)));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
console.error(`serving ${out} at http://localhost:${server.port}`);
|
|
61
|
+
let pending: ReturnType<typeof setTimeout> | null = null;
|
|
62
|
+
watch(config.root, { recursive: true }, (_event, file): void => {
|
|
63
|
+
if (!file || /(^|\/)(node_modules|dist|\.dotframe|\.git)\//.test(`${file}/`) || file.startsWith(t.out ?? "dist")) return;
|
|
64
|
+
if (pending) clearTimeout(pending);
|
|
65
|
+
pending = setTimeout((): void => {
|
|
66
|
+
runSteps(ctx, config.root, t.steps, "export-web").then(
|
|
67
|
+
(): void => console.error(`rebuilt (${file})`),
|
|
68
|
+
(e: unknown): void => console.error(e instanceof Error ? e.message : e),
|
|
69
|
+
);
|
|
70
|
+
}, 150);
|
|
71
|
+
});
|
|
72
|
+
await new Promise((): void => {});
|
|
73
|
+
}
|