castle-web-cli 0.4.83 → 0.4.85

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 (85) hide show
  1. package/dist/agent-failures.d.ts +17 -0
  2. package/dist/agent-failures.js +151 -0
  3. package/dist/agent.d.ts +26 -0
  4. package/dist/agent.js +317 -74
  5. package/dist/ide.js +35 -13
  6. package/dist/native/loop.js +35 -0
  7. package/dist/native/openrouter.d.ts +7 -0
  8. package/dist/native/openrouter.js +25 -1
  9. package/dist/native/types.d.ts +2 -0
  10. package/dist/native/types.js +0 -38
  11. package/dist/openrouter-catalog.d.ts +28 -0
  12. package/dist/openrouter-catalog.js +299 -0
  13. package/dist/shell/assets/{index-C9Zhmien.js → index-BJLaUTJE.js} +60 -58
  14. package/dist/shell/assets/{index-BMkQt27u.css → index-DonnH--m.css} +1 -1
  15. package/dist/shell/index.html +2 -2
  16. package/kits/physics-2d/.prettierrc +8 -0
  17. package/kits/physics-2d/CLAUDE.md +329 -0
  18. package/kits/physics-2d/behaviors/Camera.jsx +43 -0
  19. package/kits/physics-2d/behaviors/Collider.jsx +199 -0
  20. package/kits/physics-2d/behaviors/Goal.jsx +29 -0
  21. package/kits/physics-2d/behaviors/Layout.jsx +53 -0
  22. package/kits/physics-2d/behaviors/Sprite.jsx +352 -0
  23. package/kits/physics-2d/behaviors/tint.js +47 -0
  24. package/kits/physics-2d/blueprints/ball.scene +14 -0
  25. package/kits/physics-2d/blueprints/block.scene +12 -0
  26. package/kits/physics-2d/blueprints/cauldron.scene +18 -0
  27. package/kits/physics-2d/blueprints/crate.scene +14 -0
  28. package/kits/physics-2d/blueprints/goal.scene +12 -0
  29. package/kits/physics-2d/castle.json +13 -0
  30. package/kits/physics-2d/docs/pxart-format.md +377 -0
  31. package/kits/physics-2d/drawings/block.pxart +25 -0
  32. package/kits/physics-2d/drawings/cauldron.pxart +113 -0
  33. package/kits/physics-2d/editors/BlueprintLibrary.jsx +247 -0
  34. package/kits/physics-2d/editors/ErrorBoundary.jsx +59 -0
  35. package/kits/physics-2d/editors/PlayOnly.jsx +31 -0
  36. package/kits/physics-2d/editors/PxArtEditor.jsx +954 -0
  37. package/kits/physics-2d/editors/SceneEditor.jsx +1681 -0
  38. package/kits/physics-2d/editors/SelectionOverlay.jsx +909 -0
  39. package/kits/physics-2d/editors/SingleEditor.jsx +122 -0
  40. package/kits/physics-2d/editors/behaviorRegistry.js +30 -0
  41. package/kits/physics-2d/editors/editorHistory.js +157 -0
  42. package/kits/physics-2d/editors/inspectorSheet.js +13 -0
  43. package/kits/physics-2d/editors/pixelCanvas.js +11 -0
  44. package/kits/physics-2d/editors/pixelEditorChrome.jsx +74 -0
  45. package/kits/physics-2d/editors/pixelGeometry.js +140 -0
  46. package/kits/physics-2d/editors/pixelInspector.jsx +633 -0
  47. package/kits/physics-2d/editors/pxArtEditorModel.js +732 -0
  48. package/kits/physics-2d/editors/pxArtPlayback.js +92 -0
  49. package/kits/physics-2d/editors/pxArtTimeline.jsx +752 -0
  50. package/kits/physics-2d/editors/pxArtTimeline.module.css +506 -0
  51. package/kits/physics-2d/editors/pxArtTools.js +232 -0
  52. package/kits/physics-2d/editors/useArtboardFit.js +102 -0
  53. package/kits/physics-2d/engine/ScenePlayer.jsx +196 -0
  54. package/kits/physics-2d/engine/SceneUI.jsx +59 -0
  55. package/kits/physics-2d/engine/assets.js +15 -0
  56. package/kits/physics-2d/engine/autoInspector.jsx +70 -0
  57. package/kits/physics-2d/engine/blueprint.js +521 -0
  58. package/kits/physics-2d/engine/collider.js +196 -0
  59. package/kits/physics-2d/engine/files.js +117 -0
  60. package/kits/physics-2d/engine/liveReload.js +88 -0
  61. package/kits/physics-2d/engine/pxart.js +1032 -0
  62. package/kits/physics-2d/engine/pxartSmooth.js +222 -0
  63. package/kits/physics-2d/engine/scene.js +686 -0
  64. package/kits/physics-2d/engine/spriteGeometry.js +32 -0
  65. package/kits/physics-2d/engine/ui.jsx +688 -0
  66. package/kits/physics-2d/engine/ui.module.css +2287 -0
  67. package/kits/physics-2d/eslint.config.js +71 -0
  68. package/kits/physics-2d/index.html +24 -0
  69. package/kits/physics-2d/main.jsx +24 -0
  70. package/kits/physics-2d/package-lock.json +2706 -0
  71. package/kits/physics-2d/package.json +42 -0
  72. package/kits/physics-2d/physics/PhysicsSystem.js +290 -0
  73. package/kits/physics-2d/physics/behaviors/AnalogStick.jsx +101 -0
  74. package/kits/physics-2d/physics/behaviors/Draggable.jsx +79 -0
  75. package/kits/physics-2d/physics/behaviors/RigidBody.jsx +55 -0
  76. package/kits/physics-2d/physics/behaviors/Slingshot.jsx +118 -0
  77. package/kits/physics-2d/physics/controls.js +79 -0
  78. package/kits/physics-2d/physics/index.js +26 -0
  79. package/kits/physics-2d/physics/matterBridge.js +126 -0
  80. package/kits/physics-2d/pnpm-lock.yaml +1761 -0
  81. package/kits/physics-2d/scenes/main.scene +12 -0
  82. package/kits/physics-2d/scenes/sandbox.scene +13 -0
  83. package/kits/physics-2d/scripts/draw.mjs +121 -0
  84. package/kits/physics-2d/vite.config.js +1 -0
  85. package/package.json +1 -1
@@ -0,0 +1,377 @@
1
+ # `.pxart` Pixel-Art Format (basic-2d kit)
2
+
3
+ This documents the `.pxart` sprite format as integrated into the `basic-2d`
4
+ kit. The format and its parser/serializer/renderer live in the kit itself, in
5
+ `engine/pxart.js` (`parseFull`, `serializeFull`, `renderSpriteFrame`, plus
6
+ the compact-form helpers `parseCompact` / `serializeCompact` / `renderToCanvas`).
7
+ `.pxart` is a pure kit-layer concern — the harness (`castle-web-sdk`, the CLI)
8
+ has no knowledge of it.
9
+
10
+ A sprite is modeled on Aseprite conventions: a `layers × frames` spreadsheet of
11
+ cells, one resolution per sprite, an ordered indexed palette, named animation
12
+ tags, and Aseprite-style linked cells. A `.pxart` file is a single JSON object.
13
+
14
+ The format has two **coexisting on-disk forms** — capability tiers, *not*
15
+ temporal versions — that the kit treats as one in-memory model:
16
+
17
+ - **full** — the full layered/animated `Sprite` (`"format": "full"`).
18
+ - **compact** — a flat `{ palette, grid }` object (`"format": "compact"`, or
19
+ absent). The parser upgrades it into a degenerate one-layer / one-frame
20
+ Sprite in memory.
21
+
22
+ > Files in this kit use the extension `.pxart`. They live under `drawings/`
23
+ > and are discovered by `engine/files.js`.
24
+
25
+ ---
26
+
27
+ ## 1. Form discriminator
28
+
29
+ - Every canonical file carries a top-level string `format`.
30
+ - `"format": "full"` — the layered/animated sprite.
31
+ - `"format": "compact"` (or absent) — the flat-grid shorthand (see
32
+ [§9](#9-compact-shorthand)). The parser upgrades it in memory.
33
+ - `serializeFull` always stamps `"format": "full"`. `serializeCompact` (the
34
+ compact path the kit's `PxArtEditor` and the `draw` generator use) always
35
+ stamps `"format": "compact"`.
36
+ - **The two forms are capability tiers, not versions** — `full` is a superset of
37
+ `compact`. Files of both kinds coexist; a sprite is written in whichever form
38
+ loses nothing (see [§9](#9-compact-shorthand)).
39
+ - **Detection is by STRUCTURE, not the field.** The parser recognizes the
40
+ compact form by an object `palette` + a `grid`, and the full form by a
41
+ `resolution` / array `palette` / `frames` / `layers`. The `format` field is a
42
+ hint used only to disambiguate genuinely ambiguous shapes; clear structure
43
+ always wins, so a file with a missing or even mislabeled discriminator still
44
+ parses (without silent mis-detection). Unknown top-level fields are ignored
45
+ (forward compatibility).
46
+
47
+ ---
48
+
49
+ ## 2. Resolution
50
+
51
+ ```json
52
+ "resolution": { "width": 16, "height": 16 }
53
+ ```
54
+
55
+ - **Default resolution is 16×16** (`DEFAULT_RESOLUTION`). New/empty sprites and
56
+ generated art start at 16×16, and the kit's `PxArtEditor` opens a blank canvas
57
+ at this size.
58
+ - **Power-of-two only**, from **16 to 512**: one of `16, 32, 64, 128, 256, 512`.
59
+ - **One resolution per sprite.** There is no per-layer or per-frame resolution.
60
+ - **Parser policy:**
61
+ - **Explicit full-form `resolution`:** each dimension is **snapped** to the
62
+ valid set (clamped to `[16, 512]`, then rounded to the nearest power of two,
63
+ ties round up). Out-of-range values snap rather than reject, keeping the
64
+ parser tolerant.
65
+ - **compact shorthand upgrade:** resolution is inferred from the grid's
66
+ **native dimensions verbatim** and is **not** snapped, so arbitrary-size
67
+ compact grids round-trip faithfully.
68
+
69
+ ---
70
+
71
+ ## 3. Palette
72
+
73
+ ```json
74
+ "palette": [
75
+ { "key": "o", "hex": "#e8b800" },
76
+ { "key": "y", "hex": "#ffe11a" }
77
+ ]
78
+ ```
79
+
80
+ - An **ORDERED array** of `{ key, hex }` entries (indexed-with-hex color mode).
81
+ - `key`: a **single character** used in packed grid strings.
82
+ - `hex`: a normalized `#rrggbb` or `#rrggbbaa`.
83
+ - **Ordering is meaningful** — it is the stable index, preserved on parse and
84
+ serialize.
85
+ - **`.` is reserved for transparent** and is **never** a palette entry. A grid
86
+ character of `.` (or any char with no matching palette key) renders
87
+ transparent.
88
+ - Indexed-with-hex only: **not** RGBA-per-pixel, **not** grayscale.
89
+
90
+ ### Two-tier palette system (Endesga-64)
91
+
92
+ The kit uses a **two-tier** fixed palette, both tiers drawn from Lospec's
93
+ [Endesga 64](https://lospec.com/palette-list/endesga-64). There is no free color
94
+ picking at either tier.
95
+
96
+ **Full palette — Endesga 64 (`EDG64`, all 64 colors): what USERS paint with.**
97
+ The kit's `PxArtEditor` exposes the **full 64-color swatch** (canonical Lospec
98
+ order). The editor assigns each color a stable single-char key (`KEY_ALPHABET`,
99
+ 64 distinct keys) plus `.` for transparent. When an existing sprite is opened for
100
+ editing, each cell's resolved color is **snapped to the nearest Endesga-64
101
+ color** by RGB distance, so editing always stays on the fixed swatch. (Sprites
102
+ still *render* with their own stored palette via `renderSpriteFrame`; the snap
103
+ only affects what the editor writes back.) The swatch UI renders as an 8-column
104
+ grid (8 rows for the 64 colors) that scrolls within the inspector.
105
+
106
+ **Agent subset — 16 colors (`AGENT_PALETTE_16`): what the LLM generates with.**
107
+ The agent generation path (the `draw` svg-rect → `.pxart` quantizer and the
108
+ prompt the model receives) is constrained to a **fixed 16-color subset of
109
+ Endesga-64**, so generated art stays coherent. Every color below is a member of
110
+ `EDG64`. In order:
111
+
112
+ ```
113
+ #e69c69 #bf6f4a #8a4836 #391f21 #891e2b #ea323c #ffa214 #ffeb57
114
+ #5ac54f #1e6f50 #134c4c #657392 #c7cfdd #ffffff #0cf1ff #0098dc
115
+ ```
116
+
117
+ So: the editor offers all 64 to people; the agent generation path snaps to these
118
+ 16. Both constants (`EDG64`, `AGENT_PALETTE_16`) live in the kit's
119
+ `engine/pxart.js`.
120
+
121
+ ---
122
+
123
+ ## 4. Frames & timing
124
+
125
+ ```json
126
+ "frames": [{ "durationMs": 120 }, { "durationMs": 120 }, { "durationMs": 240 }],
127
+ "defaultDurationMs": 120
128
+ ```
129
+
130
+ - `frames`: an array of `{ durationMs }`. Its length is the **frame count**.
131
+ Frame indices are 0-based.
132
+ - `defaultDurationMs`: applied to any frame missing an explicit `durationMs`
133
+ (the kit default is 100ms).
134
+ - **No global `fps` field** (matches Aseprite). Holding a frame longer is just a
135
+ larger `durationMs`.
136
+
137
+ The kit's `Sprite` behavior advances frames in its `update(dt)` using each
138
+ frame's `durationMs`.
139
+
140
+ ---
141
+
142
+ ## 5. Tags
143
+
144
+ ```json
145
+ "tags": [
146
+ { "name": "idle", "from": 0, "to": 1, "direction": "pingpong", "repeat": 0 },
147
+ { "name": "blink", "from": 2, "to": 2, "direction": "forward", "repeat": 1 }
148
+ ],
149
+ "defaultTag": "idle"
150
+ ```
151
+
152
+ - `tags`: named, inclusive 0-based frame ranges. Each tag has:
153
+ - `name`: stable handle.
154
+ - `from`, `to`: inclusive frame indices.
155
+ - `direction`: `"forward" | "reverse" | "pingpong"`.
156
+ - `repeat`: play count; **`0` = loop forever**.
157
+ - `defaultTag`: name of the tag that plays by default.
158
+
159
+ **Animation lives in the file** — ranges and timing are the asset's intrinsic
160
+ property. The placement/runtime layer may override *which* tag plays:
161
+
162
+ ### Actor-side tag override (this kit)
163
+
164
+ The `Sprite` behavior exposes a `tag` prop. Resolution order:
165
+
166
+ 1. If `tag` is a non-empty string naming a tag in the file, that tag plays.
167
+ 2. Otherwise the file's `defaultTag` plays.
168
+ 3. Otherwise the whole timeline plays forward, looping.
169
+
170
+ `direction` and `repeat` come from the tag (a whole-timeline fallback loops
171
+ forever, forward). When `repeat` is exhausted the behavior holds the last frame.
172
+ When the `playing` prop is `false`, the behavior holds frame 0.
173
+
174
+ ---
175
+
176
+ ## 6. Layers
177
+
178
+ ```json
179
+ "layers": [
180
+ {
181
+ "id": "body", "name": "Body", "visible": true, "opacity": 1.0,
182
+ "blendMode": "normal", "kind": "pixel",
183
+ "cells": [ ... ]
184
+ }
185
+ ]
186
+ ```
187
+
188
+ - `layers`: an **ordered** array, **bottom → top** (`layers[0]` drawn first).
189
+ - Each layer: `id`, `name`, `visible` (skipped when hidden), `opacity`
190
+ (`0.0`–`1.0`), `blendMode` (**`"normal"` only** for now), `kind`
191
+ (**`"pixel"` only** for now), and `cells` (its row of the spreadsheet).
192
+
193
+ `renderSpriteFrame` composites visible layers bottom→top, applying per-layer
194
+ opacity, at 1px/cell with `imageSmoothingEnabled = false`.
195
+
196
+ ---
197
+
198
+ ## 7. Cells
199
+
200
+ `cells` is **dense-with-`null`**: `layer.cells.length == frames.length`, and
201
+ cell index `f` is the layer's content for frame `f`. A cell is one of:
202
+
203
+ | Form | Meaning |
204
+ | --- | --- |
205
+ | `{ "grid": ["...", "..."] }` | An image: packed strings using palette keys, one per row. `.` = transparent. |
206
+ | `{ "link": <frameIndex> }` | Aseprite-style **same-layer** reference; shares the image of `cells[frameIndex]`. Edits to the source propagate. |
207
+ | `null` | Empty / fully transparent for this (layer, frame). |
208
+
209
+ - A `link` must point at a cell index on the **same layer**; the resolver
210
+ (`resolveCellGrid`) follows the chain to the underlying `{ grid }`, guarding
211
+ against cycles and invalid indices (resolving to `null` if it can't reach a
212
+ grid).
213
+ - **Faithful storage:** grids are stored as **packed strings** of palette keys.
214
+ Generation-only emission shapes (svg-rect, etc.) normalize into this packed
215
+ form and are never the thing of record.
216
+
217
+ ---
218
+
219
+ ## 8. Coordinates
220
+
221
+ - **Origin is top-left.** `grid[0]` is the top row; char index 0 is the leftmost
222
+ column.
223
+ - Units are **pixels** within the sprite's `resolution`. Scaling/placement
224
+ happen **outside** the file — in this kit, the actor's `Layout` box. The
225
+ `Sprite` behavior blits the native-resolution frame into the Layout rectangle
226
+ with smoothing disabled and an optional `tint` multiply.
227
+
228
+ ---
229
+
230
+ ## 9. compact shorthand
231
+
232
+ A file with a top-level `palette` (an **object** map) + `grid` (no `layers` /
233
+ `frames`) is the **compact flat-grid shorthand**:
234
+
235
+ ```json
236
+ {
237
+ "format": "compact",
238
+ "palette": { "y": "#ffe11a", "o": "#e8b800", ".": null },
239
+ "grid": ["..ooyyoo..", ".oyyyyyyo.", "oyyyyyyyyo"]
240
+ }
241
+ ```
242
+
243
+ It is **sugar for one layer / one frame**, upgraded in memory by `parseFull`
244
+ (via `upgradeCompactToFull`):
245
+
246
+ - `resolution` ← the grid's native `{ width, height }` (not snapped).
247
+ - `palette` ← the compact record as an ordered array (insertion order), dropping
248
+ the `.`/null transparent entry.
249
+ - one `frame` using `defaultDurationMs`.
250
+ - one `pixel` layer whose single cell is `{ grid: <the grid> }`.
251
+
252
+ This keeps cold-start generation tiny: emit just `{ palette, grid }` and the
253
+ system treats it as a full (degenerate) Sprite.
254
+
255
+ ### Single-layer / single-frame generation contract
256
+
257
+ The **generation contract** for this kit is to emit the **compact shorthand**: a
258
+ single 16×16 (default) `{ palette, grid }` sprite over the agent 16-color subset
259
+ (`AGENT_PALETTE_16`).
260
+ The CLI generation worker and the kit's `PxArtEditor` both produce/store this
261
+ compact form when nothing is lost by it. Multi-layer / multi-frame structure is
262
+ authored by tools later, not hand-generated — and the minimal `PxArtEditor`
263
+ opens such files **read-only** (it only edits the single-layer / single-frame
264
+ case).
265
+
266
+ ### compact ↔ full relationship
267
+
268
+ - `full` is a **superset** of `compact`; the compact functions keep their exact
269
+ behavior.
270
+ - A compact file is equivalent to a full sprite with one `pixel` layer and one
271
+ frame.
272
+ - `parseFull` accepts **both** forms and always yields a full `Sprite`.
273
+ - On save, the kit picks the form **structurally**: it serializes the working
274
+ sprite to the candidate compact form, re-parses it, and keeps the compact
275
+ output only when that round-trip reproduces the sprite exactly (`serializeModel`
276
+ in `editors/pxArtEditorModel.js`). A single fully-opaque visible layer / single
277
+ inheriting frame / no tags / all on-canvas content round-trips losslessly, so
278
+ it is stored compact. Anything the compact form can't carry — extra
279
+ layers/frames/tags, per-layer opacity or visibility, cel offsets, or off-canvas
280
+ content — forces `serializeFull`. This is a derived fact, not a hand-maintained
281
+ checklist, so it stays correct as new full-form features are added.
282
+
283
+ ---
284
+
285
+ ## 10. Corner rounding
286
+
287
+ ```json
288
+ "cornerRadius": 0.25
289
+ ```
290
+
291
+ - A top-level **number**: a corner-rounding radius, in native-pixel units.
292
+ `0` (the default) renders sharp/nearest-neighbor, same as before this field
293
+ existed. Any value `> 0` renders rounded, by that amount — see "Rendering"
294
+ below. **File-level** — a property of the sprite asset itself, not a
295
+ per-actor/per-placement override. There is no equivalent `Sprite` behavior
296
+ prop.
297
+ - **A value, not a flag**, deliberately: the amount of rounding is itself
298
+ part of the portable file format, not a fixed code-side constant every
299
+ smooth sprite would otherwise be stuck with. `MAX_CORNER_RADIUS` (0.5, in
300
+ `engine/pxart.js`) is the ceiling — two corner cuts on the same
301
+ 1-native-pixel edge must not overlap — and any value is clamped to
302
+ `[0, MAX_CORNER_RADIUS]` on read. The `PxArtEditor` UI curates this down to
303
+ a 3-way segmented control (`0` / `¼` / `½`, see `CornerRadiusBar` in
304
+ `editors/pixelInspector.jsx`) so picking a value never means hunting a
305
+ slider, but the format itself isn't limited to those three — any in-range
306
+ number is valid, e.g. from hand-edited JSON or a future finer-grained UI.
307
+ - **`0` is the default** and is never written to disk: `serializeFull` and
308
+ `serializeCompact` both OMIT the field when it's `0`, so existing (and
309
+ newly authored, unsmoothed) files stay byte-identical to their
310
+ pre-`cornerRadius`-field shape.
311
+ - **Present in BOTH on-disk forms.** `parseCompact` reads a top-level
312
+ `cornerRadius` alongside `palette`/`grid`; `parseFull`'s native-full branch
313
+ reads it alongside `resolution`/`layers`; `upgradeCompactToFull` carries a
314
+ compact file's `cornerRadius` through into the upgraded Sprite. This means
315
+ the kit's save path — which picks compact vs. full by a serialize →
316
+ re-parse round-trip (`serializeModel` in `editors/pxArtEditorModel.js`, see
317
+ [§9](#9-compact-shorthand)) — never drops the field: a single-layer/frame
318
+ rounded sprite still serializes compact, with `cornerRadius: 0.25` alongside
319
+ `palette`/`grid`.
320
+ - **Parser tolerance:** any non-finite-number value (missing field, `null`, a
321
+ typo) defaults to `0`. Numbers are clamped to `[0, MAX_CORNER_RADIUS]`.
322
+ **Legacy back-compat:** this field used to be called `render` — first a
323
+ `"pixel"` | `"smooth"` string enum, then (briefly) a bare numeric radius
324
+ under that same key. Both migrate on read: a legacy `render: "smooth"`
325
+ becomes a fixed radius (`0.25`); a legacy numeric `render` value is read
326
+ as-is; anything else (including the original `render: "pixel"`) defaults to
327
+ `0`. This keeps old files rendering rounded rather than silently reverting
328
+ to sharp. Detection of compact vs. full form ([§1](#1-form-discriminator))
329
+ is unaffected — neither key is ever used as a structural signal.
330
+ - **Rendering.** `cornerRadius === 0` renders as before: 1px/cell,
331
+ nearest-neighbor (`renderSpriteFrame`, `imageSmoothingEnabled = false`).
332
+ `cornerRadius > 0` renders through `engine/pxartSmooth.js`'s
333
+ `renderSmoothSpriteFrame`, using a LOCAL per-pixel kernel — the same shape
334
+ of algorithm as Animal Crossing's actual smoothing (xBRZ-style template
335
+ matching), not a global vectorization pass: it composites the frame
336
+ normally (so layer visibility/opacity/blend keep working), then for each
337
+ pixel independently, classifies each of its 4 corners against only the 3
338
+ pixels touching that corner (its two edge-adjacent neighbors and their
339
+ shared diagonal neighbor). A corner rounds only when it's a genuine convex
340
+ corner of that pixel's own color region — a diagonal touch between two
341
+ same-colored pixels is deliberately left sharp so it doesn't get visually
342
+ pinched off, and a corner already matched by an adjacent same-color
343
+ neighbor is left for that neighbor's own (independent) classification to
344
+ handle. Because the kernel never looks past a pixel's immediate
345
+ neighborhood, there's no notion of a "run" or "line" to (mis)detect across
346
+ a whole shape — every pixel supersamples into its own fixed block of the
347
+ output canvas (by default 8x the sprite's native resolution), so rounding
348
+ a corner is always a same-block recoloring, never a shape that could leave
349
+ a gap against its neighbors. This suits small pixel-art sprites, not
350
+ general raster upscaling.
351
+ - `behaviors/Sprite.jsx` blits a `cornerRadius > 0` sprite's cached canvas
352
+ with `imageSmoothingEnabled = true` (vs. `false` for `cornerRadius === 0`),
353
+ passing the sprite's own `cornerRadius` value through as
354
+ `renderSmoothSpriteFrame`'s `cornerRadius` option; the offscreen canvas
355
+ cache itself is unaffected (still one WeakMap entry per sprite object —
356
+ smoothing is a property of the sprite, not an extra cache dimension). The
357
+ `PxArtEditor` artboard previews rounded sprites live (a display-resolution
358
+ canvas behind the pixel-grid editing surface) while keeping normal
359
+ pixel-grid editing/selection interactions on the grid underneath.
360
+
361
+ ---
362
+
363
+ ## 11. Reserved / out of scope
364
+
365
+ Named so the format can grow without a breaking change, but **not built**:
366
+ tilemap/tileset layers (a future `kind`), slices, layer groups, vector/avatar
367
+ layers, and rich blend modes (beyond `"normal"`). Reserving the `kind` and
368
+ `blendMode` discriminators keeps these additive later, not breaking.
369
+
370
+ ---
371
+
372
+ ## Appendix — sample
373
+
374
+ `drawings/cauldron.pxart` is a 16×16 multi-layer animated sprite referenced by
375
+ the `pig` actor in `scenes/main.scene` (via `blueprints/cauldron.scene`)
376
+ through the `Sprite` behavior — a working rendered example of the format in
377
+ this kit.
@@ -0,0 +1,25 @@
1
+ {
2
+ "format": "compact",
3
+ "palette": {
4
+ "a": "#c7cfdd",
5
+ "b": "#657392"
6
+ },
7
+ "grid": [
8
+ "aaaaaaaaaaaaaaaa",
9
+ "bbbbbbbbbbbbbbbb",
10
+ "bbbbbbbbbbbbbbbb",
11
+ "bbbbbbbbbbbbbbbb",
12
+ "bbbbbbbbbbbbbbbb",
13
+ "bbbbbbbbbbbbbbbb",
14
+ "bbbbbbbbbbbbbbbb",
15
+ "bbbbbbbbbbbbbbbb",
16
+ "bbbbbbbbbbbbbbbb",
17
+ "bbbbbbbbbbbbbbbb",
18
+ "bbbbbbbbbbbbbbbb",
19
+ "bbbbbbbbbbbbbbbb",
20
+ "bbbbbbbbbbbbbbbb",
21
+ "bbbbbbbbbbbbbbbb",
22
+ "bbbbbbbbbbbbbbbb",
23
+ "bbbbbbbbbbbbbbbb"
24
+ ]
25
+ }
@@ -0,0 +1,113 @@
1
+ {
2
+ "format": "full",
3
+ "resolution": {
4
+ "width": 16,
5
+ "height": 16
6
+ },
7
+ "palette": [
8
+ {
9
+ "key": "I",
10
+ "hex": "#5ac54f"
11
+ },
12
+ {
13
+ "key": "1",
14
+ "hex": "#622461"
15
+ },
16
+ {
17
+ "key": "2",
18
+ "hex": "#93388f"
19
+ }
20
+ ],
21
+ "frames": [
22
+ {},
23
+ {}
24
+ ],
25
+ "defaultDurationMs": 500,
26
+ "tags": [],
27
+ "cornerRadius": 0.25,
28
+ "layers": [
29
+ {
30
+ "id": "layer-0",
31
+ "name": "Layer 1",
32
+ "visible": true,
33
+ "opacity": 1,
34
+ "blendMode": "normal",
35
+ "kind": "pixel",
36
+ "cells": [
37
+ {
38
+ "grid": [
39
+ "................",
40
+ "................",
41
+ "................",
42
+ "...11111111111..",
43
+ ".211IIIIIIIII11.",
44
+ ".22IIIIIIIII221.",
45
+ "..22222222222...",
46
+ "...1111111111...",
47
+ "..222222221111..",
48
+ ".22222222221111.",
49
+ ".22222222221111.",
50
+ ".22222222221111.",
51
+ ".22222222211111.",
52
+ "..222222211111..",
53
+ ".11222211111111.",
54
+ ".11.11111111.11."
55
+ ]
56
+ },
57
+ {
58
+ "link": 0
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "id": "layer-1",
64
+ "name": "Layer 1 copy",
65
+ "visible": true,
66
+ "opacity": 1,
67
+ "blendMode": "normal",
68
+ "kind": "pixel",
69
+ "cells": [
70
+ {
71
+ "grid": [
72
+ "...........I....",
73
+ "....I...........",
74
+ ".......I........",
75
+ "................",
76
+ "................",
77
+ "................",
78
+ "................",
79
+ "................",
80
+ "................",
81
+ "................",
82
+ "................",
83
+ "................",
84
+ "................",
85
+ "................",
86
+ "................",
87
+ "................"
88
+ ]
89
+ },
90
+ {
91
+ "grid": [
92
+ "...I............",
93
+ "........I.......",
94
+ "............I...",
95
+ "................",
96
+ "................",
97
+ "................",
98
+ "................",
99
+ "................",
100
+ "................",
101
+ "................",
102
+ "................",
103
+ "................",
104
+ "................",
105
+ "................",
106
+ "................",
107
+ "................"
108
+ ]
109
+ }
110
+ ]
111
+ }
112
+ ]
113
+ }