pixelclaw 0.0.0-stage → 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/README.md +19 -2
- package/dist/LICENSE +21 -0
- package/dist/THIRD_PARTY_LICENSES.txt +545 -0
- package/dist/pixelclaw.js +666 -0
- package/dist/skill/SKILL.md +255 -0
- package/dist/skill/commands.md +840 -0
- package/dist/skill/manifest.md +64 -0
- package/dist/ui/assets/index-DacOg_uL.css +1 -0
- package/dist/ui/assets/index-DxowuS_Y.js +53 -0
- package/dist/ui/index.html +14 -0
- package/package.json +50 -4
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
# Command reference (language version 2)
|
|
2
|
+
|
|
3
|
+
PixelClaw documents are edited with a small line-based command language. The same text works from
|
|
4
|
+
the CLI (`pixelclaw run`), the MCP server (`run`) and the watch view, whose tools write these
|
|
5
|
+
commands too. Every command is written to the asset's tape in its **normal form** (defaults filled
|
|
6
|
+
in, colours by name), so replaying the tape gives the same pixels even if a default changes later.
|
|
7
|
+
|
|
8
|
+
## Syntax
|
|
9
|
+
|
|
10
|
+
- One command per line, or several on a line separated by `;`.
|
|
11
|
+
- `#` starts a comment at the start of a line or after a space. `c=#aabbcc` is a colour, not a
|
|
12
|
+
comment.
|
|
13
|
+
- Points are `x,y` from the top-left (negative values are allowed). Sizes are `WxH`.
|
|
14
|
+
- Options are `key=value`; flags are bare words (`fill`, `outline`).
|
|
15
|
+
- `@cell(cx,cy)` after the command name offsets every coordinate to that cell of the cell grid.
|
|
16
|
+
- On a `wrap=on` document, coordinates past an edge wrap around; otherwise they are clipped.
|
|
17
|
+
- Fractions (`level=`, `density=`) are written `0.25`, `3/16` or `25%`. Ranges are `2..4`.
|
|
18
|
+
- `{ ... }` holds a macro's body (see *Macros*); it may span lines and hold `;`.
|
|
19
|
+
|
|
20
|
+
### Colours
|
|
21
|
+
|
|
22
|
+
`c=` takes a palette entry **name** (`c=stone3`), its one-character **key** (`c=3`), the exact
|
|
23
|
+
**hex** of an entry (`c=#6c7570`), or `none` (transparent). A hex that is not in the palette is
|
|
24
|
+
refused: `c=#ff00ff is not in palette 'mossy-ruins'`. The tape always records the name.
|
|
25
|
+
|
|
26
|
+
### Batches and errors
|
|
27
|
+
|
|
28
|
+
A `run` is one batch, logged as one undo group. If any line has an error, nothing in the batch is
|
|
29
|
+
applied, and every error is reported with its line and column:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
floor.pclaw:2:10: error: unknown colour 'gold' (palette 'mossy-ruins': ink, stone0, ...)
|
|
33
|
+
floor.pclaw:3:10: error: line: expected an end point x,y
|
|
34
|
+
2 errors; nothing was applied.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Authors and groups
|
|
38
|
+
|
|
39
|
+
Every tape line records its author (`by`): `claude` for the agent, `human:<name>` for a person in
|
|
40
|
+
the watch view. Each author has their own **editing context** in the document: the layer and
|
|
41
|
+
frame they draw on, their selection, their live symmetry and their brush. `layer`, `frame`,
|
|
42
|
+
`select`, `mirror` and `brush` change only the author's own context, so the agent's selection
|
|
43
|
+
never clips a person's strokes, and a person choosing a layer never moves the agent. An author
|
|
44
|
+
who has not chosen draws on the *home* layer and frame (at first the bottom layer, `main`, and
|
|
45
|
+
frame 1; they move with their layer and frame when others are added, moved or removed). Groups are numbered per kind of author, each with its own counter: the agent's
|
|
46
|
+
(and scripts') groups are `g1`, `g2`, …, and people's are `h1`, `h2`, …. One action in the watch
|
|
47
|
+
view (a pencil drag, a fill, a move, a palette edit, a note) is one group.
|
|
48
|
+
|
|
49
|
+
### Autotile families
|
|
50
|
+
|
|
51
|
+
A **family** is a sheet of every variant an autotile scheme can produce, with the two
|
|
52
|
+
textures they share. `new family shore 24x24 scheme=blob47 [map=14x12]` makes one: the size is
|
|
53
|
+
the tile's (square, even); the canvas is the sheet. Row 0 holds the `inside` texture (the
|
|
54
|
+
family's terrain) and the `outside` texture; then the variants, in the scheme's order, so many to
|
|
55
|
+
a row; then, with `map=`, a coverage map of that many tiles to paint. Cells are named:
|
|
56
|
+
`@cell(inside)`, `@cell(outside)`, `@cell(<variant>)` work in every drawing command, and `view
|
|
57
|
+
family` lists where each is.
|
|
58
|
+
|
|
59
|
+
**Schemes** are data (`core/schemes/*.json`): which neighbours count, each variant's name and the
|
|
60
|
+
reduced neighbourhood it answers to, and how it is built from four 12x12 (half-tile) quadrants.
|
|
61
|
+
|
|
62
|
+
| Scheme | Neighbours | Variants | Pieces (drawn by hand) |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `blob47` | 8, a corner counts only when both sides beside it are the terrain | 47 | `o`, `nesw`, `ew`, `ns`, `nesw_ne_se_sw_nw` |
|
|
65
|
+
| `wang16-corner` | the tile's four corners; the edge runs through the middle of the tile | 16 | 7 (see the file) |
|
|
66
|
+
| `path` | 4; a strip through the middle (inset a quarter of the tile) | 16 | `o`, `nesw`, `ew`, `ns` |
|
|
67
|
+
| `bridge8` | along one axis, east-west first | 8 (`v`, the north-south single, is placed by name) | `h`, `h_ew`, `v`, `v_ns` |
|
|
68
|
+
| `single` | none | 1 | `o` |
|
|
69
|
+
| `face` | blob47, plus a cliff's faces in the cells around the terrain: south faces below it, side faces beside its east and west edges, corners where they meet, alternate looks of the long middles | 47 + 28 | blob47's and `face_o`, `face_ew`, `face_ie_iw`, `side_e`, `side_e_ns`, `side_w`, `side_w_ns`, `corner_ew` |
|
|
70
|
+
| `lane` | 8; a path that fills where it is wide (a corner counts when both sides beside it are the lane) and turns diagonally (a lone diagonal draws a strip to that corner); inset a quarter of the tile | 82 + 15 | `o`, `nesw`, `ew`, `ns`, `nesw_ne_se_sw_nw` and 9 for the diagonals (see the file) |
|
|
71
|
+
|
|
72
|
+
Variant names are the joined sides (`n e s w`, `o` for none), then each counted corner:
|
|
73
|
+
`ne_ne` is joined north and east with the corner between them, `nesw_ne_se_sw_nw` the interior.
|
|
74
|
+
|
|
75
|
+
**Faces.** The face scheme's faces are art in cells outside the terrain, drawn by hand on the
|
|
76
|
+
pieces' band layer (the band leaves them alone) and copied by quadrant to the rest:
|
|
77
|
+
|
|
78
|
+
- *South faces*, in the cell below the terrain. Each half ends, joins the face beside it (`e`, `w`:
|
|
79
|
+
that cell is below the terrain too) or meets the terrain beside it, an inner corner (`ie`, `iw`):
|
|
80
|
+
`face_o`, `face_e`, `face_w`, `face_ew`, `face_ie`, `face_iw`, `face_e_iw`, `face_ie_w`, `face_ie_iw`.
|
|
81
|
+
Pieces: `face_o` (both ends), `face_ew` (the middle), `face_ie_iw` (both inner corners).
|
|
82
|
+
- *Side faces*, in the cell beside the terrain and not below it: `side_e…` on the cell's west half
|
|
83
|
+
(the terrain's east side), `side_w…` on its east half, `side_ew…` both; `_n` joins the face above
|
|
84
|
+
(the terrain goes on above), `_s` the one below (the cell below is not terrain), else the strip
|
|
85
|
+
ends there. Pieces: `side_e` (both ends), `side_e_ns` (the middle), `side_w`, `side_w_ns`.
|
|
86
|
+
- *Corners*, where the terrain is only diagonally above: `corner_e` (below its east side, past the
|
|
87
|
+
south face's end), `corner_w`, `corner_ew`. Piece: `corner_ew`.
|
|
88
|
+
- *Alternate looks* of the long middles: `face_ew_b`, `face_ew_c`, `side_e_ns_b`, `side_w_ns_b`. A
|
|
89
|
+
map (and the sample island) puts one of the looks on each such cell by a hash of the cell. Each is
|
|
90
|
+
a copy of its middle until you `family detach face_ew_b` and draw it, so a long cliff need not
|
|
91
|
+
repeat one tile.
|
|
92
|
+
|
|
93
|
+
**Lanes.** `lane` is for roads and rivers that are sometimes wide: a block of lane cells has
|
|
94
|
+
a plain middle (the `path` scheme, four neighbours, shows the outside at every inside corner of a
|
|
95
|
+
wide area), and a lane can step diagonally: a cell joined only across a corner draws a strip to it
|
|
96
|
+
(`o_se`), the cells beside the step carry the strip's edge (`d_…`, drawn in a cell outside the lane
|
|
97
|
+
like a fillet), and the strip is measured across each row so its edges step cleanly at 45°.
|
|
98
|
+
|
|
99
|
+
**How the variants are made.** A family has two layers. `ground` holds the textures; on every
|
|
100
|
+
variant it is laid again after every command: each pixel shows the inside or the outside texture
|
|
101
|
+
*at the same pixel of the tile*, by the band geometry, so every variant tiles with the plain
|
|
102
|
+
textures and with each other (the textures are *re-projected*). `band` holds the band's art: the
|
|
103
|
+
**pieces** are drawn by hand (the agent and people draw on `band` by default), and every other
|
|
104
|
+
variant's band is assembled from the pieces' quadrants after every command, so detail drawn on a
|
|
105
|
+
piece shows at once on every variant that uses that quadrant. A variant can be **detached** to
|
|
106
|
+
draw it by hand.
|
|
107
|
+
|
|
108
|
+
| Command | Normal form | Notes |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `family texture inside\|outside from=<tile> [frame=<n>]` | `family texture inside from=sand frame=1 <<` rows `>>` | Copies a tile asset (the family's tile size; colours matched by name, else by exact colour) into the texture cell on your frame. The line records the pixels, so replay needs no other asset; a block of rows can be given instead of `from=`. **The family follows the tile**: when the tile changes, the server copies it in again, on each family frame whose latest texture line for that side came `from=` it (one undo group by the same author, the author's frame put back), like `variant of=<base> band=inherit`. A texture drawn from rows is the family's own and is left alone. |
|
|
111
|
+
| `family band <w>:<fill> ... [radius=] [inset=] [jitter=<n> [seed=]] [draw=on\|off]` | `family band 3:outside 1:foam 2:sand0 radius=3 inset=0 jitter=2 seed=6 draw=on` | **Route 1, a band profile**: the band from the terrain's edge inward, each part a width and a colour, `outside` (the outside texture carries on) or `inside`; then the inside texture. `radius` rounds the band's inner edge at outer corners (the outer contours are wider arcs about the same centre); `inset` moves the edge in from the tile's (path and bridge strips; default the scheme's). Inset, band and radius must fit in half a tile. With `draw=on` it draws the colours on every piece's band layer (clearing what was there; the face scheme's face pieces are left alone, they are drawn by hand); `draw=off` only sets the geometry. `jitter=<n>` moves every band edge in or out by up to n px, along each edge, by a seeded field that repeats every tile, so the edges are not ruler-straight and the tiles still join; the seed is written in (try several with the `seeds` tool). |
|
|
112
|
+
| `family band from=<family>` | `family band from=shore 3:outside 1:foam radius=3 inset=0 detached=none frames=1 cells=o,nesw,ew,ns,nesw_ne_se_sw_nw <<` rows `>>` | Copies another family's band (same scheme and tile): its geometry, its detached variants and its band layer. The line records it, so replay reads no other asset. It records only the cells the family does not build from its pieces (`cells=`: the pieces, the detached variants and any other cell with band art, their rows side by side, frame after frame); the other variants are built again from the pieces after the line, as the source's were. On a 24 px blob47 sheet a sync went from 33 KB of tape to 4 KB. The older form without `cells=` (the whole sheet's rows) still replays. A texture variant with `variant of=<base> band=inherit` gets this line after every change to the base (see *Oversized assets, maps and scenes*). |
|
|
113
|
+
| `family detach\|attach <variant>\|all` | `family detach ne_ne` | A detached variant's band layer is its own (drawn by hand or cut); attach builds it from the pieces again. Pieces are always drawn by hand. |
|
|
114
|
+
| `family coverage [seed=] [copies=] [fill=on\|off]` | `family coverage seed=7 copies=1 fill=off` | Lays out the coverage map so every variant the scheme can produce appears `copies` times (a seeded search; refused, with what to try, when the map is too small: blob47 needs about 14x12), and draws its flat template (inside, outside and the band's colours) on a reference layer `template`. `fill=on` also paints the map with the current variants, to start from. |
|
|
115
|
+
| `family cut [textures=on\|off]` | `family cut textures=off` | **Route 3, cut from a painted map**: every variant takes the medoid of the map tiles that resolve to it (the tile with the fewest differing band pixels to the others), keeping only its band (`family band … draw=off` says how wide; the textures stay the shared ones), and is detached. `textures=on` also takes the textures from the map's all-inside and all-outside tiles. |
|
|
116
|
+
|
|
117
|
+
**Route 2, quadrant pieces**: draw the pieces themselves (`paint @cell(o) 0,0 << … >>` on layer
|
|
118
|
+
`band`); every variant follows. Animated families use frames as any document does: copy each
|
|
119
|
+
frame of an animated texture into the same frame of the family, and link the band layer's cels
|
|
120
|
+
(`cel link 1-4 layer=band`) so hand detail shows on every frame.
|
|
121
|
+
|
|
122
|
+
**Overlays** are small sprites played over a map's tiles: `new overlay glint 5x5`, its
|
|
123
|
+
frames and ticks as usual, then
|
|
124
|
+
|
|
125
|
+
| Command | Normal form | Notes |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `overlay set [mode=] [on=] [anchor=] [spread=] [every=] [rest=] [seed=]` | `overlay set mode=sparkle on=outside anchor=centre spread=8x8 every=3 rest=150 seed=11` | `mode`: `cycle` and `pingpong` (every instance in step), `wave` (the phase moves across the map), `sparkle` (each instance plays once, then rests `rest` ticks, at its own moment). `on`: the family's outside tiles, its terrain, or all. `anchor`: the overlay's top-left in the tile (default centred); `spread`: a seeded offset per tile up to that many pixels either way; `every`: one tile in that many. |
|
|
128
|
+
|
|
129
|
+
`view map` shows a family on a random island (or a tile repeated), with overlays given in
|
|
130
|
+
`with=`; see *Views*.
|
|
131
|
+
|
|
132
|
+
### Oversized assets, maps and scenes
|
|
133
|
+
|
|
134
|
+
**Oversized tiles and objects** are any asset bigger than a cell of the grid:
|
|
135
|
+
`new object house 48x48 cell=24x24` is two cells by two. Its **footprint** says where it stands on
|
|
136
|
+
a map:
|
|
137
|
+
|
|
138
|
+
| Command | Normal form | Notes |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `footprint <C>x<R> [at=<cx,cy>] [anchor=<x,y>] [<< flags >>]` | `footprint 2x2 at=0,0 anchor=0,1 <<` rows `>>` | The footprint is `C` x `R` cells of the art grid (the canvas in cells), starting at art cell `at` (default: the bottom-left of the art). `anchor` is the footprint cell it is placed by (default its bottom-left). The block has a row per art row, a character per art cell: `.` nothing, `b` blocks movement, `r` a roof or canopy (drawn above actors), `x` both. Footprint cells that do not block are walkable. Without a block, the footprint blocks and nothing is a roof. `footprint off` removes it (an asset without one stands on its whole art grid, by its bottom-left cell). |
|
|
141
|
+
| `variant of=<asset> [band=inherit\|own]`, `variant off` | `variant of=lane band=inherit` | This asset is a **texture variant** of another: maps pick among an asset and its variants cell by cell (a hash of the cell, moved on when it would repeat both the cell to the left and the one above). Make one with `fork`, then change the inside (keep the border, or redraw a family's band and move its detail). The `variant` check compares their colour mix. **`band=inherit`** (families): the band stays the base's: the variant's band layer is locked, its own band edits (`family band`, `cut`, `detach`, `attach`) are refused, and after every batch that changes the base the server writes `family band from=<base>` on the variant's tape (one undo group, by the same author), so later fixes reach it; only its textures are its own. `band=own` makes the band its own again. |
|
|
142
|
+
|
|
143
|
+
A 24x48 tree with `footprint 1x1 << r / b >>` stands on one cell; its crown overhangs the cell
|
|
144
|
+
above as a canopy. The `fit` check flags an empty footprint cell and art outside the footprint
|
|
145
|
+
that is not a roof.
|
|
146
|
+
|
|
147
|
+
**Map documents** are grids of cells that hold names, not pixels: `new map village
|
|
148
|
+
40x30 tile=24` is 40 x 30 cells of 24 px. Its canvas is the grid of cells (one "pixel" per cell,
|
|
149
|
+
so notes and coordinates are cells), and its layers say what they hold:
|
|
150
|
+
|
|
151
|
+
| Command | Normal form | Notes |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `new map <name> <C>x<R> [tile=<T>] [palette=]` | `new map village 40x30 tile=24 palette=ruins-shore <<` rows `>>` | Starts with one tiles layer, `floor`. `tile=` defaults to the project's grid (project.json `grid`, 24 unless set). |
|
|
154
|
+
| `layer add <name> terrain=<family> [outside=on\|off] [walkable=on\|off] [border=same\|terrain\|not] [mix=<tile>:<weight>,... [variants=spread\|named] [seed=]]` | `layer add sea terrain=shore outside=on above=floor` | A terrain layer: cells are the family's terrain or not; when the map is drawn the family's scheme picks every edge tile, exactly as `resolveMap` does for sample maps. With `outside=on` the cells outside the terrain show the family's outside texture (the layer fills the map); otherwise they show what is below (a face scheme's faces are drawn either way). **`walkable=on`**: the filled ground and its edges are ground, not sea: objects may stand on them (the `footprint` check). **`border=`**: past the map's edge a cell counts as the edge cell's own value (`same`, the default: terrain that runs off the map has no edge band along the border), as terrain, or as not terrain (`not`, the original rule: a band runs round the terrain at the border). **`mix=`**: the terrain's fully inside cells are drawn as a seeded scatter of one-cell tiles by weight instead of the family's inside (each tile's weight shared with its texture variants unless `variants=named`, as `tiles fill mix=`); each cell's tile comes from a hash of its place, drawn again when it would equal both the cell to its left and the one above, so painting terrain elsewhere does not reshuffle it. Edges still come from the family, so mix tiles that match the family's inside texture join cleanly. `layer set <name> walkable=on\|off`, `border=…`, `mix=…\|none` change them. |
|
|
155
|
+
| `tiles <tiles layer> fill <tile>\|<family>.<variant> [<x,y> <W>x<H>] [replace]` | `tiles floor fill grass 0,0 40x30 cells=1x1 anchor=0,0` | A one-cell tile on every empty cell of the layer (or the box): the floor in one line. `replace` also covers one-cell tiles already there; multi-cell tiles stay. The cells are worked out when the line is written, so replay reads no other asset; the map picks among the tile's texture variants cell by cell. |
|
|
156
|
+
| `tiles <tiles layer> fill mix=<tile>:<weight>,... [<x,y> <W>x<H>] [replace] [variants=spread\|named] [seed=]` | `tiles floor fill mix=bed:1,bed_b:2,bed_c:1 0,0 12x8 cells=1x1 anchor=0,0 variants=named seed=812` | A seeded scatter of several one-cell tiles by weight (a weight left out is 1): each cell draws a tile, leaving out the tile to its left and the one above where another is left to draw, weighted by what each tile still has to lay of its share, so the counts follow the weights as closely as that rule allows (a tile over half the mix cannot avoid equal neighbours). **Two tiles**: that rule left no choice, a strict checkerboard; a cell now leaves out only the tile that would complete a 2x2 block of one, so two looks scatter (a two-tile line written by an older version replays the new way). Each tile's weight is shared evenly with its texture variants the mix does not name (`variants=spread`, the default): `mix=bed:3,gravel:1` with bed's variants bed_b and bed_c is written `mix=bed:1,bed_b:1,bed_c:1,gravel:1`. The line is written with the shared-out weights, `variants=named` and the seed, so it replays without reading any asset. Each cell shows exactly the tile drawn for it (the map does not pick among its variants again). Fixes a failing `lattice`. |
|
|
157
|
+
| `layer add <name> tiles`, `layer add <name> objects [shadow=darker\|<colour>\|off] [shadowdir=<dir>]` | `layer add props objects shadow=darker shadowdir=se above=lanes` | `shadow=`: every object placed on the layer casts the `shadow` helper's ellipse on the ground under it, before any object is drawn: `darker` takes each ground pixel a step down its ramp (else the nearest entry at least 6 L* darker), a colour paints it. Left out, the style guide's `style.shadow` (none without one); `layer set <name> shadow=…\|off [shadowdir=]` changes it. The map checks read the ground without shadows. | Things placed by name. Objects on every objects layer are drawn in order of their footprint's bottom row (then layer, then column), above all the ground; roof cells are drawn again last. A footprint may not overlap another on any objects layer (on a tiles layer, on that layer). |
|
|
158
|
+
| `layer add <name> overlay=<overlay> on=<where>` | `layer add glints overlay=glint on=sea:out` | An overlay played over the cells `on=` names: a terrain layer (its terrain), `<layer>:out` (outside it), or `all`. |
|
|
159
|
+
| `terrain <layer> rect <x,y> <W>x<H> [erase]` | `terrain sea rect 0,20 40x10` | Also `ellipse <x,y> <W>x<H>`, `brush <x,y>... [size=<n>]` (a line through the points), `fill <x,y>` (4-connected), `paint <x,y> <<` rows of `#` (terrain), `-` (not) and `.` (leave) `>>`, and `clear`. |
|
|
160
|
+
| `place <asset>\|<family>.<variant> <x,y> [layer=] [cells=<C>x<R> anchor=<x,y>]` | `place house 12,8 layer=props cells=2x2 anchor=0,1` | By its anchor cell, on your layer or `layer=`. The line records the footprint the asset had, so replay reads no other asset. A family's variant by name (`shore.ns`, bridge8's `bridge.v`) goes on a tiles layer. |
|
|
161
|
+
| `move <x,y> <x,y> [layer=]` | `move 20,15 20,18 layer=props` | Moves what is placed on the first cell (any cell of its footprint) so its anchor is on the second. |
|
|
162
|
+
| `remove <x,y> [layer=]` | `remove 20,18 layer=props` | Takes away what is placed on that cell. |
|
|
163
|
+
| `road <terrain layer> <x,y> <x,y> ... [width=<n>]` | `road lanes 4,4 20,4 20,12 width=1` | Lays the terrain along x, then y, between each pair of points (4-connected, so path schemes join it up). |
|
|
164
|
+
| `scatter <asset>[,<asset>] [<x,y> <W>x<H>] count=<n> [on=<terrain layer>] [off=<terrain layer>] [minDist=<n>] [layer=] [seed=]` | `scatter tree count=22 on=meadow off=lanes minDist=2 layer=props fp=1x1:0,0 seed=31` | Places at seeded spots, each footprint on `on=`'s terrain, off `off=`'s, free, and `minDist` cells (Chebyshev) from the others; fewer when no more fit. `fp=` records each asset's footprint (cells and anchor). On other documents `scatter` is the stamp generator. |
|
|
165
|
+
| `map gen island\|village\|dungeon [layers=<a>,<b>,...] [objects=<asset>[:<n>],...] [layer=<objects layer>] [seed=]` | `map gen village layers=sea,meadow,lanes objects=well,house:2 seed=8` | Lays out the terrain layers for a quick test scene (then edit it): island, the land (seeded value noise, smoothed: no one-cell spits); village, the land, a ground two cells in from the shore and lanes crossing it; dungeon, rooms joined by corridors on the first layer. Default layers: the map's terrain layers in order. `objects=` stands objects (n of each, default 1) on the objects layer (default: the map's first): larger footprints first, every footprint cell on the inner ground (village: off the lanes; island: a cell in from the shore; dungeon: a cell in from the walls), with a free cell between any two. Without `objects=`, a map the manifest plans stands the drawn objects and sprites its `uses` lists, once each; the line records them, with their footprints (`fp=`), so replay needs neither the manifest nor the assets. |
|
|
166
|
+
|
|
167
|
+
Drawing commands (`px`, `rect`, …) are refused on a map. `view map` shows a map whole at 1x, a
|
|
168
|
+
region of cells (`region=`) or the camera (`camera=x,y`: the engine's screen, project.json
|
|
169
|
+
`style.camera`, default 16x9 cells of the project's grid: `384x216` at 24 px, `768x432` at 48, around that cell) at the game's draw scale, with the cells as
|
|
170
|
+
text; `export` writes a map for Tiled (the `tiled` profile) and splits an oversized asset (see docs/mcp.md and docs/profiles.md).
|
|
171
|
+
|
|
172
|
+
## Commands
|
|
173
|
+
|
|
174
|
+
| Command | Normal form | Notes |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `new <kind> <name> <W>x<H> [palette=] [wrap=] [cell=] [bg=] [<< rows >>]` | `new tile floor 24x24 palette=mossy-ruins wrap=on cell=24x24 bg=none <<` palette rows `>>` | First command of a tape only. Kinds: `tile`, `sprite`, `sheet`, `icon`, `object`, `family`, `overlay` (see *Autotile families*), `map` (see *Oversized assets, maps and scenes*). `wrap` defaults to `on` for tiles, `off` otherwise. `cell` defaults to the canvas size. `palette` defaults to the project's default palette, and the line records the whole palette in a block (see *The palette in the tape*). The size (and palette, a family's scheme, an object's cell grid) may be left out for an asset the manifest plans (`new sprite urn`), or a kind the style guide sizes (`style.sizes`); the line records what was filled in ([pipeline.md](pipeline.md)). |
|
|
177
|
+
| `px <x,y> [<x,y> ...] c=` | `px 3,4 4,4 c=ink` | Single pixels. |
|
|
178
|
+
| `line <x,y> <x,y> [<x,y> ...] c= [pp=on\|off]` | `line 3,10 9,7 c=stone2 pp=on` | A line or polyline. See *Lines* below. |
|
|
179
|
+
| `curve <x,y> <x,y> <x,y> [<x,y>] c= [pp=on\|off]` | `curve 2,20 8,12 16,14 c=soil2 pp=on` | A quadratic (3 points) or cubic (4 points) Bezier. See *Curves and polygons*. |
|
|
180
|
+
| `rect <x,y> <W>x<H> c= [outline\|fill]` | `rect 2,2 20x6 c=stone3 outline` | Outline by default. |
|
|
181
|
+
| `ellipse <cx,cy> r=<RX>x<RY> c= [outline\|fill]` | `ellipse 12,12 r=5x3 c=moss2 outline` | Centre and radii (`r=5` means `r=5x5`); spans `2*RX+1` pixels. |
|
|
182
|
+
| `ellipse <x,y> <W>x<H> c= [outline\|fill]` | `ellipse 2,2 6x4 c=moss2 fill` | Inscribed in a box, so even sizes work. |
|
|
183
|
+
| `polygon <x,y> <x,y> <x,y> [...] c= [outline\|fill]` | `polygon 1,1 14,4 10,14 c=moss2 fill` | Closed. See *Curves and polygons*. |
|
|
184
|
+
| `fill <x,y> c= [mode=contiguous\|global] [conn=4\|8]` | `fill 7,9 c=stone3 mode=contiguous conn=4` | Flood fill (crosses edges on wrap documents). `mode=global` replaces every pixel of that colour. |
|
|
185
|
+
| `paint <x,y> <<` rows `>>` | see below | An ASCII block of palette keys. |
|
|
186
|
+
| `stroke pencil c= [pp=on\|off] <x,y> ...` | `stroke pencil c=ink pp=on 12,4 12,5 13,6` | One freehand drag through the sampled points. See *Strokes*. |
|
|
187
|
+
| `stroke eraser <x,y> ...` | `stroke eraser 3,3 4,4` | The same, clearing to transparent. |
|
|
188
|
+
| `layer add <name> [above=\|below=<layer>] [reference]` | `layer add rim above=main` | A new empty layer, by default above yours. See *Layers and frames*. |
|
|
189
|
+
| `layer <name>` | `layer rim` | Draw on that layer from now on (you only). |
|
|
190
|
+
| `layer set <name> [hidden=] [locked=] [reference=]` | `layer set rim locked=on` | Each `on` or `off`; only the flags given change. |
|
|
191
|
+
| `layer rename <name> <new>`, `layer move <name> above=\|below=<layer>`, `layer merge <name>`, `layer remove <name>` | `layer move rim below=main` | `merge` draws the layer onto the one below, in every frame, and removes it. |
|
|
192
|
+
| `frame add [<n>] [copy=<m>\|empty]` | `frame add 2 copy=1` | A new frame at position n (default: last), a copy of frame m (default: yours) or empty. Frames count from 1. |
|
|
193
|
+
| `frame <n>` | `frame 2` | Draw on (and see) that frame from now on (you only). |
|
|
194
|
+
| `frame remove <n>`, `frame move <n> to=<m>` | `frame move 3 to=1` | |
|
|
195
|
+
| `frame add [<n>] link=<m>` | `frame add link=1` | A new frame whose cels are linked to frame m's. |
|
|
196
|
+
| `frame set <n>\|<a>-<b>\|all ticks=<t>` | `frame set 1-4 ticks=12` | Durations in game ticks (the project's `tickRate`, 60 by default); new frames take their source's (default 10). |
|
|
197
|
+
| `tag add <name> <a>-<b> [loop=cycle\|pingpong]` | `tag add idle 1-2 loop=pingpong` | A named run of frames. Also `tag set <name> [frames=<a>-<b>] [loop=]`, `tag remove <name>`. |
|
|
198
|
+
| `view <dir> frames=<a>-<b>` | `view S frames=1-2` | A direction (S N E W SE SW NE NW) and its frames. `view remove <dir>`. |
|
|
199
|
+
| `view <dir> mirror=<dir>\|off` | `view W mirror=E` | A live mirror: the other view flipped left to right, kept up to date; its frames are added at the end. |
|
|
200
|
+
| `cel link <a>-<b> [layer=<name>\|all]`, `cel unlink …` | `cel link 1-4 layer=main` | Linked cels share their pixels (drawing on one draws on all). |
|
|
201
|
+
| `pivot <x,y>\|off`, `ground <y>\|off` | `pivot 11,22` | The pixel the sprite stands on and the row its feet rest on. |
|
|
202
|
+
| `select rect <x,y> <W>x<H> [add\|sub\|and]` | `select rect 2,2 6x4` | Select a rectangle. See *Selections*. |
|
|
203
|
+
| `select ellipse <cx,cy> r=<RX>x<RY>`, `select ellipse <x,y> <W>x<H>` | `select ellipse 8,8 r=6x4` | |
|
|
204
|
+
| `select lasso <x,y> <x,y> <x,y> ...` | `select lasso 1,1 14,3 9,14` | A polygon (pixel centres inside, and its edge). |
|
|
205
|
+
| `select wand <x,y> [mode=contiguous\|global] [conn=4\|8]` | `select wand 2,2 mode=contiguous conn=4` | Touching pixels of that palette index on your layer (or all of them). |
|
|
206
|
+
| `select colour c=<colour>` | `select colour c=moss2` | Every pixel of that colour on your layer. |
|
|
207
|
+
| `select layer [<name>]` | `select layer main` | The opaque pixels of a layer (default: yours). |
|
|
208
|
+
| `select all`, `select none`, `select invert`, `deselect` | | |
|
|
209
|
+
| `clear` | `clear` | Clear the selected pixels of your layer (or all of it) to transparent. |
|
|
210
|
+
| `shift <dx,dy> [sel] [wrap]` | `shift 2,-1`, `shift 5,0 wrap` | Move the selected pixels (or your whole layer). See *Transforms*. |
|
|
211
|
+
| `flip h\|v [sel]` | `flip h` | Mirror them within their box. |
|
|
212
|
+
| `rotate 90\|180\|270 [sel]` | `rotate 90` | Clockwise, about the middle of their box. |
|
|
213
|
+
| `mirror [x=<a>] [y=<b>] live` | `mirror x=11.5 live` | Live symmetry: later strokes are mirrored until `mirror off`. See *Symmetry*. |
|
|
214
|
+
| `mirror x=<a> from=left\|right`, `mirror y=<b> from=top\|bottom` | `mirror x=11.5 from=left` | Copy one side over the other, once. |
|
|
215
|
+
| `dither <x,y> <W>x<H> a= b= [pattern=] [level=]` | `dither 0,0 24x8 a=pool b=moss1 pattern=bayer4 level=0.25` | See *Dithering and gradients*. |
|
|
216
|
+
| `gradient <x,y> <W>x<H> ramp= [dir=] [steps=] [pattern=]` | `gradient 0,0 24x24 ramp=moss dir=down steps=5 pattern=none` | |
|
|
217
|
+
| `ramp <name> = <entry> <entry> ...` | `ramp wood = wood0 wood1 wood2` | Define or replace a ramp of this document's palette, darkest first. |
|
|
218
|
+
| `outline c= [mode=outer\|inner] [corners=on\|off] [from=<layer>]` | `outline c=ink mode=outer corners=off` | See *Helpers*. Without `c=`: the style guide's outline colour (`style.outline`). |
|
|
219
|
+
| `shade light= [ramp=<ramp>\|all] [edge=<n>]` | `shade light=nw ramp=wood edge=1` | Without `light=`: the style guide's (`style.light`). |
|
|
220
|
+
| `clean orphans [size=<n>] [keep=none\|texture]` | `clean orphans size=1 keep=none` | |
|
|
221
|
+
| `shadow [<x,y> <W>x<H>] [c=<colour>] [dir=<dir>]` | `shadow 8,20 12x4 c=soil0 dir=se` | A shadow under a sprite or an object: a flat ellipse on the ground as wide as the art's bottom third and two pixels more, a third as tall, centred on its lowest row and moved a sixth of its width toward `dir` (default the style guide's `style.shadow.dir`, else away from its light); drawn on your layer only where the art is empty, so it sits behind. `c=` defaults to `style.shadow.c` (an entry; `darker` is for maps, where there is ground under it). The line records the box. |
|
|
222
|
+
| `noise [<x,y> <W>x<H>] ramp= ...` | `noise 0,0 24x24 ramp=moss weights=0,3,12,1,0 cluster=1..2 density=0.07 base=moss2 wrap=on seed=7` | See *Generators*. |
|
|
223
|
+
| `scatter [<x,y> <W>x<H>] stamp=\|c= count= ...` | `scatter 0,0 24x24 stamp=tuft count=3 minDist=10 flip=on wrap=on seed=3` | |
|
|
224
|
+
| `voronoi [<x,y> <W>x<H>] cells= ramp= ...` | `voronoi 0,0 24x24 cells=7 ramp=stone weights=1,1,1,1,1,1 gap=ink relax=1 wrap=on seed=5` | |
|
|
225
|
+
| `bricks [<x,y> <W>x<H>] size=<W>x<H> ramp= ...` | `bricks 0,0 24x24 size=8x4 ramp=stone weights=1,1,1,1,1,1 gap=ink offset=4 seed=4` | |
|
|
226
|
+
| `pebbles [<x,y> <W>x<H>] ramp= ...` | `pebbles 0,0 24x24 ramp=stone weights=1,1,1,1 size=3..5 density=0.3 light=nw cast=none gap=1 wrap=on edges=3 seed=3` | Stones lit from the style's light. See *Generators*. |
|
|
227
|
+
| `tufts [<x,y> <W>x<H>] ramp= ...` | `tufts 0,0 48x48 ramp=moss size=8..12 blades=3..5 density=0.2 light=nw foot=moss0 gap=1 wrap=on edges=3 seed=3` | Grass tufts lit from the style's light. |
|
|
228
|
+
| `stamp save <name> <x,y> <W>x<H>`, `stamp save <name> <<` rows `>>` | `stamp save tuft 4,4 3x3` | See *Stamps and brushes*. |
|
|
229
|
+
| `stamp <name> <x,y> [flip=none\|h\|v\|hv]` | `stamp tuft 10,12 flip=none` | Placed by its top-left corner. |
|
|
230
|
+
| `stamp remove <name>` | | |
|
|
231
|
+
| `brush square <n>`, `brush circle <n>`, `brush stamp <name>`, `brush off` | `brush circle 3` | |
|
|
232
|
+
| `def <name>(<param>,...) { ... }` | `def tuft(x,y) { px $x,$y c=moss3 }` | A macro. See *Macros*. |
|
|
233
|
+
| `call <name> <args...> [seed=<n>]` | `call tuft 5,18` | |
|
|
234
|
+
| `palette set <entry> hex=#rrggbb` | `palette set stone3 hex=#7a8a80` | Change a colour of this asset's palette. See *Palette edits*. |
|
|
235
|
+
| `palette add [<name>] hex=#rrggbb [key=<k>]` | `palette add gold hex=#e0b040 key=G` | Add a colour to this asset's palette. |
|
|
236
|
+
| `palette lock`, `palette unlock` | | See *Palette locking*. |
|
|
237
|
+
| `palette save as=<name>` | `palette save as=ruins-props` | Write this asset's palette to the project as a new palette. |
|
|
238
|
+
| `note <x,y> [<W>x<H>] "text"` | `note 12,5 "beak too long" id=n1` | Pin a note to a pixel or a region. See *Notes*. |
|
|
239
|
+
| `note done <id>` | `note done n1` | Close a note. |
|
|
240
|
+
| `mark <name> ["text"]` | `mark reviewed "fixed the rim"` | A named point on the tape; `reviewed`, `approved`, `rejected` set the status. See *Marks*. |
|
|
241
|
+
| `undo [group=<g>] [by=<author>]` | `undo group=g4` | See *Undo and redo* below. |
|
|
242
|
+
| `redo [group=<g>] [by=<author>]` | `redo group=h5` | Take back an undo. |
|
|
243
|
+
| `fork <from> <to> [at=<n>]` | `fork chest chest_b at=20` | Start asset `<to>` as a branch of `<from>`. See *Forks*. |
|
|
244
|
+
| `underlay generate "<prompt>" [layout=] [image=] [provider=] [model=] [quality=] [cap=] [layer=]`, `underlay import <file.png> [layout=] [layer=]` | `underlay mockup blob=<sha256> image=1536x1024 at=0,0 size=144x96 layout=<sha256> prompt="…" provider=openai model=gpt-image-1.5 quality=medium cost=0.05` | An image under the art on a reference layer; resolved by the server. See *Underlays and snap*. |
|
|
245
|
+
| `snap [from=<layer>] [into=<layer>]` | `snap from=mockup into=draft at=0,0 grid=144x96 pitch=5.333x5.334 offset=0.66,0.65 r2=0.97 material=0.98 <<` | The underlay quantised into a new draft layer. |
|
|
246
|
+
| `import <x,y> asset=<name> region=<x,y,WxH> [layer=]` | `import 0,0 from="yard:draft 120,0,24x24" <<` | A region of another asset, its pixels in the line. |
|
|
247
|
+
| `new font <name> <W>x<H> [chars=] [cols=] [baseline=] [line=] [spacing=]` | `new font hud 5x7 palette=… wrap=off cell=5x7 bg=none chars=65-90,48-57 cols=16 baseline=6 line=8 spacing=1 <<` | A glyph sheet. See *Fonts*. |
|
|
248
|
+
| `glyph <char> [advance=<n>\|auto] [<< rows >>]` | `glyph U+0021 <<` | One glyph's cell (and its advance). |
|
|
249
|
+
| `font set [baseline=] [line=] [spacing=]` | `font set baseline=6 line=9 spacing=1` | |
|
|
250
|
+
| `icon mode=tone\|full\|channel` | `icon mode=full` | How an icon is exported. See *Icons*. |
|
|
251
|
+
|
|
252
|
+
Commands that draw (everything that writes pixels) draw on your layer and frame, clipped by your
|
|
253
|
+
selection, and are refused while your layer is locked: `layer 'rim' is locked (layer set rim
|
|
254
|
+
locked=off to draw on it)`.
|
|
255
|
+
|
|
256
|
+
### Lines
|
|
257
|
+
|
|
258
|
+
Lines are pixel-perfect:
|
|
259
|
+
|
|
260
|
+
- Each step moves one pixel along the major axis, so a line is 8-connected with no doubled
|
|
261
|
+
L-corners.
|
|
262
|
+
- The pixels are shared out evenly among the rows (or columns) the line crosses: `0,0 11,3` is
|
|
263
|
+
runs of 3-3-3-3 and `0,0 8,2` is 3-3-3. Uneven counts are spread symmetrically (`0,0 6,2` is
|
|
264
|
+
2-3-2).
|
|
265
|
+
- The pixels depend only on the two endpoints, not on the direction drawn.
|
|
266
|
+
- With `pp=on` (the default), a polyline drops the doubled L-corner pixels where its segments
|
|
267
|
+
join. The exception is a right angle between straight horizontal and vertical segments, which
|
|
268
|
+
is kept. `pp=off` keeps every joint pixel.
|
|
269
|
+
|
|
270
|
+
### Paint blocks
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
paint 8,4 <<
|
|
274
|
+
..kkk..
|
|
275
|
+
.k333k.
|
|
276
|
+
k34443k
|
|
277
|
+
>>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Each character is a palette key. `.` leaves the pixel as it is, and a space clears it to
|
|
281
|
+
transparent. The rows' shared indentation is removed. When spaces at the start or end of a row
|
|
282
|
+
matter, wrap every row in `|` margins; the text between the margins is used exactly:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
paint 0,0 <<
|
|
286
|
+
| k |
|
|
287
|
+
|k k|
|
|
288
|
+
>>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The normal form uses a two-space indent, or `|` margins when a row starts or ends with a space.
|
|
292
|
+
|
|
293
|
+
### Strokes
|
|
294
|
+
|
|
295
|
+
`stroke` is what the pencil and eraser write: one drag, one line, one group. The points are the
|
|
296
|
+
pixels the pointer passed over, in order; consecutive points are joined by pixel-perfect segments.
|
|
297
|
+
With `pp=on` (the default) a doubled L-corner is dropped wherever the path makes one, as
|
|
298
|
+
Aseprite's pixel-perfect pencil does, so a drag that steps right and then down draws a diagonal.
|
|
299
|
+
Unlike `line`'s polylines, no right angle is kept. `pp=off` keeps every pixel. `stroke eraser`
|
|
300
|
+
takes no colour and never drops pixels. A stroke may run past the canvas: it clips, or wraps on a
|
|
301
|
+
`wrap=on` document.
|
|
302
|
+
|
|
303
|
+
### Layers and frames
|
|
304
|
+
|
|
305
|
+
Layers are listed bottom to top. The flat image (what views, previews and exports show) takes the
|
|
306
|
+
topmost non-transparent pixel of the **art layers**: those that are neither hidden nor
|
|
307
|
+
`reference`. A reference layer is a guide or an underlay for tracing: views and the watch view
|
|
308
|
+
show it faded underneath the art, and it is never exported. `layer add` puts a new empty layer
|
|
309
|
+
above yours (or `above=`/`below=` another); the normal form always names the place. Layer names
|
|
310
|
+
are letters, digits, `_` and `-` (not `add`, `set`, `rename`, `move`, `merge` or `remove`).
|
|
311
|
+
|
|
312
|
+
Frames are the animation: every layer has a cel per frame. `frame add` inserts a
|
|
313
|
+
copy of your frame (or `copy=m`, `link=m`, or `empty`) and `frame <n>` chooses the frame you draw
|
|
314
|
+
on. Views show the frame the agent draws on (`frame=` in the MCP `view`, `--frame` in the CLI);
|
|
315
|
+
`preview.png` shows frame 1.
|
|
316
|
+
|
|
317
|
+
### Animation
|
|
318
|
+
|
|
319
|
+
- **Durations** are in game ticks: `frame set 2 ticks=6`, `frame set all ticks=12`. The project
|
|
320
|
+
sets the tick rate (`tickRate` in project.json, 60 a second); playback, the `anim` view and its
|
|
321
|
+
GIF run at it. A frame added later takes the ticks of the frame it copies.
|
|
322
|
+
- **Tags** name runs of frames (`walk`, `idle`) and say how they loop: `cycle` (1 2 3 1 2 3) or
|
|
323
|
+
`pingpong` (1 2 3 2 1 2 3). A frame added strictly inside a tag (or a view) makes it longer;
|
|
324
|
+
removing frames shortens it, and one left with no frames goes.
|
|
325
|
+
- **Views** are directions: `view S frames=1-2`, `view N frames=3-4`, `view E frames=5-6`. Views
|
|
326
|
+
never overlap, and a frame cannot be moved out of its view. `view W mirror=E` makes W a **live
|
|
327
|
+
mirror** of E: W gets as many frames as E (added at the end the first time), each of its cels is
|
|
328
|
+
E's flipped left to right, and its durations are E's. It follows every change to E (drawing,
|
|
329
|
+
frames added or removed, ticks) after every command. Drawing on a mirror frame is refused with
|
|
330
|
+
the frame to draw on instead; `view W mirror=off` keeps W's pixels as its own. Only views facing
|
|
331
|
+
the other way left to right mirror each other (E/W, SE/SW, NE/NW).
|
|
332
|
+
- **Linked cels** are one cel shown in several frames: `cel link 1-4` gives frames 2-4 of your
|
|
333
|
+
layer frame 1's pixels, shared, so drawing on any of them draws on all; `cel unlink 2-3` gives
|
|
334
|
+
each its own copy again. `layer merge` unlinks the layer below first.
|
|
335
|
+
- **Sequences.** Playback, the `anim` view and the `anim` checks step through every tag, every view
|
|
336
|
+
with no tag inside it, or (with neither) all the frames as one cycle.
|
|
337
|
+
- **Pivot and ground line**: `pivot 11,22` (the pixel the sprite stands on, an export field) and
|
|
338
|
+
`ground 22` (the row the feet rest on). The `anim` check measures ground drift from the ground
|
|
339
|
+
line (or the first frame's lowest row) and pivot drift as the silhouette's centre column moving
|
|
340
|
+
between frames. Both are drawn in cyan in the anim sheet and the watch view.
|
|
341
|
+
|
|
342
|
+
### Selections
|
|
343
|
+
|
|
344
|
+
Every author has their own selection. While it exists, **every drawing command that author
|
|
345
|
+
writes is clipped to it**, flood fills and generators included (`fill` starts only inside the
|
|
346
|
+
selection and never leaves it; `mode=global` replaces only selected pixels). Nobody else's drawing
|
|
347
|
+
is clipped by it. `deselect` (or `select none`) ends it.
|
|
348
|
+
|
|
349
|
+
- `select rect`, `select ellipse` (centre and radii, or a box) and `select lasso` (a polygon:
|
|
350
|
+
pixel centres inside it, plus its edge) select shapes; on a `wrap=on` document they may cross an
|
|
351
|
+
edge and wrap.
|
|
352
|
+
- `select wand x,y` selects the pixels of that palette index touching x,y on your layer, as it is
|
|
353
|
+
when the line runs (`conn=8` counts diagonals; `mode=global` takes every pixel of the index).
|
|
354
|
+
`select colour c=…` is the same as a global wand by colour. `select layer` takes a layer's
|
|
355
|
+
opaque pixels in your frame.
|
|
356
|
+
- A trailing `add`, `sub` or `and` combines the new shape with your current selection (union,
|
|
357
|
+
difference, intersection). `select invert` swaps selected and unselected; `select all` selects
|
|
358
|
+
everything. A selection that ends up empty is no selection.
|
|
359
|
+
- `clear` sets the selected pixels of your layer to transparent.
|
|
360
|
+
|
|
361
|
+
The watch view's selection tools write these commands as soon as a selection is made, so a
|
|
362
|
+
selection is undone and redone like any edit.
|
|
363
|
+
|
|
364
|
+
### Transforms
|
|
365
|
+
|
|
366
|
+
`shift dx,dy`, `flip h|v` and `rotate 90|180|270` act on the selected pixels of your layer, or, with
|
|
367
|
+
nothing selected, on all of it. The pixels move (transparent ones too); the places they leave
|
|
368
|
+
become transparent; the selection moves with them. Past an edge they clip, or wrap on a `wrap=on`
|
|
369
|
+
document.
|
|
370
|
+
|
|
371
|
+
- `flip` mirrors them within their bounding box; `rotate` turns them clockwise about the middle of
|
|
372
|
+
the box (a quarter turn swaps the box's sides, keeping its middle, rounded up and left).
|
|
373
|
+
- `shift dx,dy wrap` wraps within the selection's box, or within the canvas with nothing selected,
|
|
374
|
+
on any document: `shift 12,0 wrap` moves a tile's seam into its middle, where you can see and
|
|
375
|
+
fix it, and `shift -12,0 wrap` moves it back.
|
|
376
|
+
- The word `sel` (`shift 0,-1 sel`, `flip h sel`) insists on a selection: with none, the command
|
|
377
|
+
is refused instead of moving the whole layer.
|
|
378
|
+
|
|
379
|
+
### Symmetry
|
|
380
|
+
|
|
381
|
+
`mirror x=11.5 live` turns on live symmetry for you: every later drawing command you write that
|
|
382
|
+
places shapes (`px`, `line`, `curve`, `rect`, `ellipse`, `polygon`, `stroke`, `paint`, `stamp`, and
|
|
383
|
+
`fill`, which starts again at each mirrored point) is drawn again mirrored across the axis, until
|
|
384
|
+
`mirror off`. The axis is a pixel position: `x=11.5` lies between pixels 11 and 12 (the middle of a
|
|
385
|
+
24 px canvas), `x=12` runs through pixel 12. `y=` adds a horizontal axis; with both, strokes are
|
|
386
|
+
drawn four times. `mirror live` alone means the vertical middle. Generators and helpers are not
|
|
387
|
+
mirrored.
|
|
388
|
+
|
|
389
|
+
`mirror x=11.5 from=left` copies the left half over the right once (`from=right` the other way;
|
|
390
|
+
`y=… from=top|bottom` for a horizontal axis), inside your selection if you have one.
|
|
391
|
+
|
|
392
|
+
### Curves and polygons
|
|
393
|
+
|
|
394
|
+
`curve` draws a quadratic Bezier through its first and last points, pulled by the middle one, or a
|
|
395
|
+
cubic with two middle points. It is sampled in exact integer arithmetic (each sample rounded to
|
|
396
|
+
the nearest pixel), joined by pixel-perfect segments, and, with `pp=on`, its doubled L-corners are
|
|
397
|
+
dropped as on a pencil stroke.
|
|
398
|
+
|
|
399
|
+
`polygon` closes its points. The outline is the closed polyline (pixel-perfect, right angles kept);
|
|
400
|
+
`fill` adds every pixel whose centre is inside (even-odd rule), so the filled shape and its outline
|
|
401
|
+
always agree. With a brush, outlines (`rect`, `ellipse`, `polygon` outline, and lines, curves and
|
|
402
|
+
strokes) draw with it; filled shapes do not.
|
|
403
|
+
|
|
404
|
+
### Dithering and gradients
|
|
405
|
+
|
|
406
|
+
Patterns are ordered threshold matrices anchored to the canvas, not to the box, so dithered areas
|
|
407
|
+
line up across a tile's edges and between boxes: `checker`, `bayer2`, `bayer4`, `bayer8`, `hlines`,
|
|
408
|
+
`vlines`.
|
|
409
|
+
|
|
410
|
+
- `dither x,y WxH a=<colour> b=<colour> pattern=… level=…` paints the box in `a`, with `b` on the
|
|
411
|
+
share `level` of its pixels (0 to 1; default 0.5; `bayer4` by default).
|
|
412
|
+
- `gradient x,y WxH ramp=<ramp> dir=down|up|right|left|radial steps=<n> pattern=…` runs along a
|
|
413
|
+
ramp from its first colour to its last (`radial`: from the middle of the box out to its corners).
|
|
414
|
+
`steps` colours of the ramp are used, spread evenly from first to last (default: all of them).
|
|
415
|
+
With `pattern=none` (the default) the bands meet with a hard edge; with a pattern, each band
|
|
416
|
+
dithers into the next. `ramp=` names a palette ramp, or lists entries: `ramp=stone1,stone2,stone4`.
|
|
417
|
+
|
|
418
|
+
### Ramps
|
|
419
|
+
|
|
420
|
+
A ramp is an ordered run of palette entries, darkest first. Palette files define
|
|
421
|
+
ramps; `ramp <name> = <entry> ...` defines or replaces one in this document's palette (entries by
|
|
422
|
+
name or key). Ramps drive `gradient`, `shade`, `noise`, `voronoi`, `bricks` and `clean ...
|
|
423
|
+
keep=texture`, and the watch view's gradient tool.
|
|
424
|
+
|
|
425
|
+
### Helpers
|
|
426
|
+
|
|
427
|
+
- `outline c=<colour>` draws a 1-px outline around the shapes on your layer: `mode=outer` (the
|
|
428
|
+
default) colours the transparent pixels next to a shape; `mode=inner` colours the shape's own
|
|
429
|
+
edge pixels. `corners=off` (the default) looks at the four side neighbours, `on` at all eight.
|
|
430
|
+
`from=<layer>` outlines the shapes on another layer, onto yours (an outline layer under the art).
|
|
431
|
+
- `shade light=<n|ne|e|se|s|sw|w|nw>` lights each shape: a pixel whose colour is in a ramp, within
|
|
432
|
+
`edge` pixels (default 1) of the shape's boundary on the lit side, goes a ramp step lighter; on
|
|
433
|
+
the far side, a step darker. A shape is a run of pixels of the same ramp; anything else is its
|
|
434
|
+
boundary. With a diagonal light, both of its sides count (nw: north and west are lit). A pixel
|
|
435
|
+
lit from one side and shaded from the other (a 1-px part) stays. `ramp=all` (the default) shades
|
|
436
|
+
each colour along the first ramp it is in.
|
|
437
|
+
- `clean orphans` replaces each pixel whose colour no 4-neighbour shares (a lone pixel; transparent
|
|
438
|
+
counts as a colour) with the colour most common around it. `size=n` takes clusters of up to n
|
|
439
|
+
pixels. `keep=texture` keeps a stray that is one ramp step from its surroundings: specks within
|
|
440
|
+
a material are texture, not mistakes.
|
|
441
|
+
|
|
442
|
+
### Generators
|
|
443
|
+
|
|
444
|
+
Every generator writes its seed into the line: give `seed=` to choose one, or leave it out and the
|
|
445
|
+
normaliser picks one (from the command's place on the tape) and records it. The same seed always
|
|
446
|
+
gives the same pixels; another seed is another variant. The box is optional (default: the whole
|
|
447
|
+
canvas). With `wrap=on` (the default on `wrap=on` documents) a generator treats the box as a torus,
|
|
448
|
+
so a texture fills a tile seamlessly by construction.
|
|
449
|
+
|
|
450
|
+
- `noise ramp=<ramp> weights=<w,...> cluster=a..b density=<share> base=<entry>|keep`: fills the box
|
|
451
|
+
with `base` (default: the entry with the largest weight; `keep` leaves the pixels as they are),
|
|
452
|
+
then grows clusters of a..b pixels (default 1..3; on cells of 48 px and up it scales with the cell, 2..6 at 48) in the other ramp colours, chosen by weight,
|
|
453
|
+
until about `density` of the box is covered (default 0.15).
|
|
454
|
+
- `scatter stamp=<name>[,<name>...] count=<n> minDist=<d>`: places stamps (chosen at random from
|
|
455
|
+
the list) at points at least `minDist` apart (on a torus with wrap), each by its middle;
|
|
456
|
+
`flip=on` flips some of them left-right. `c=<colour>` scatters single pixels instead.
|
|
457
|
+
- `voronoi cells=<n> ramp=<ramp> weights=… gap=<colour>|none relax=<n>`: stones. Each pixel
|
|
458
|
+
belongs to the nearest of n spread-out points (moved `relax` times to their cell's centre,
|
|
459
|
+
default 1); each cell takes a ramp colour by weight, and `gap` draws 1-px joints between cells.
|
|
460
|
+
Follow it with `shade` for bevelled stones.
|
|
461
|
+
- `bricks size=<W>x<H> ramp=<ramp> weights=… gap=<colour>|none offset=<n>`: courses of bricks
|
|
462
|
+
W by H with 1-px joints (included in the size), every other course shifted by `offset` (default
|
|
463
|
+
half a brick). A box a whole number of bricks wide repeats seamlessly.
|
|
464
|
+
- `pebbles ramp=<ramp> weights=… size=a..b density=<share> gap=<n> light=<dir> cast=<colour>|none`
|
|
465
|
+
: stones. Each is an ellipse a..b px wide (default from the cell: 3..4 at 16 px, 3..5 at
|
|
466
|
+
24, 5..11 at 48) in a body tone of the ramp (by `weights`, one per entry that has a darker and a
|
|
467
|
+
lighter neighbour), its shadow side one step darker on the edges facing away from the light, and a
|
|
468
|
+
lit pixel one step lighter toward it (a pair on small stones, two on stones 8 px wide or more).
|
|
469
|
+
`light=` defaults to the style guide's (`style.light`), else `nw`. Stones never overlap: `gap`
|
|
470
|
+
(default 1) is kept clear between them. `cast=<colour>` also lays a cast shadow on the ground,
|
|
471
|
+
away from the light.
|
|
472
|
+
- `tufts ramp=<ramp> size=a..b blades=a..b density=<share> gap=<n> light=<dir> foot=<colour>|none`
|
|
473
|
+
: grass. Each tuft is a few blades from a row of roots (default 2..3 blades at 16 px, 3..4
|
|
474
|
+
at 24, 3..5 from 32), the middle one tallest, the outer ones leaning out; blades on the light's
|
|
475
|
+
side take the ramp's lightest colour, those on the far side a darker step, and `foot` (default
|
|
476
|
+
the ramp's darkest) a row under the roots.
|
|
477
|
+
|
|
478
|
+
Both place their shapes so the rows and columns come out even (the measure `repetition` and
|
|
479
|
+
`seam` use: each shape tries a few spots and the places around it and keeps the evenest, and may
|
|
480
|
+
take another of its tones or heights), over whatever is in the box already; ground of a shape's own
|
|
481
|
+
colour right beside it (a speck of noise) takes the box's commonest colour so it does not join the
|
|
482
|
+
shape. With `wrap=on` the shapes that cross the box's edge come from `edges=<seed>` (default the
|
|
483
|
+
seed itself) and the rest from `seed=`: looks of one ground drawn with the same `edges=` and other
|
|
484
|
+
seeds share everything across their edges, so they join each other any way round. That is the way
|
|
485
|
+
to make texture variants of a structured ground:
|
|
486
|
+
|
|
487
|
+
```
|
|
488
|
+
new tile shingle 24x24 bg=soil1
|
|
489
|
+
pebbles ramp=stone seed=4 # writes edges=4
|
|
490
|
+
fork shingle shingle_b
|
|
491
|
+
variant of=shingle
|
|
492
|
+
rect 0,0 24x24 c=soil1 fill
|
|
493
|
+
pebbles ramp=stone edges=4 seed=9 # the same stones across the edges, others inside
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`wrap=off` keeps every shape whole inside the box (for a box inside a tile).
|
|
497
|
+
|
|
498
|
+
### Stamps and brushes
|
|
499
|
+
|
|
500
|
+
A stamp is a piece of art kept in the document. `stamp save <name> x,y WxH` copies a box of your
|
|
501
|
+
layer (with a selection, unselected pixels are left out); `stamp save <name> <<` takes a paint
|
|
502
|
+
block (`.` and space are transparent). `stamp <name> x,y` draws it with its top-left at x,y; its
|
|
503
|
+
transparent pixels leave the canvas alone; `flip=h|v|hv` mirrors it. `scatter` places stamps
|
|
504
|
+
procedurally, and `brush stamp <name>` draws with one's shape.
|
|
505
|
+
|
|
506
|
+
`brush square <n>` and `brush circle <n>` (1 to 32 px) make lines, curves, strokes, single pixels
|
|
507
|
+
and outlines draw a square or round dot at every pixel; `brush stamp <name>` draws the stamp's
|
|
508
|
+
opaque shape, in the command's colour; `brush off` (or size 1) goes back to single pixels. The
|
|
509
|
+
brush is yours alone and stays until you change it.
|
|
510
|
+
|
|
511
|
+
### Macros
|
|
512
|
+
|
|
513
|
+
```
|
|
514
|
+
def tuft(x,y) { px $x,$y $x+1,$y-1 c=moss3 ; px $x+2,$y c=moss2 }
|
|
515
|
+
call tuft 5,18
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
`def` keeps a body of commands in the document; `call` runs it with values for its parameters (a
|
|
519
|
+
point `5,18` fills two). In the body, `$x` is a parameter, `$x+1` is arithmetic on it (`+ - * /`,
|
|
520
|
+
left to right, `/` rounding down), and `$(x*2+y)` is any integer expression of the parameters.
|
|
521
|
+
Words work too: `def row(y,c) { line 0,$y 23,$y c=$c }` then `call row 4 moss1`. A body may span
|
|
522
|
+
lines and call other macros (8 deep at most); it may not hold `new`, `fork`, `undo`, `redo`, `note`
|
|
523
|
+
or `def`. The `def` is checked when it is written (its body must parse).
|
|
524
|
+
|
|
525
|
+
The tape records the `def` (body normalised: shared indentation removed) and each `call` as one
|
|
526
|
+
line, not its expansion; replay expands it again. A generator in a body without `seed=` takes
|
|
527
|
+
its seed from the call's: the call's line then records `seed=<n>`, and `call … seed=<n>` with
|
|
528
|
+
another number gives another variant of the whole macro.
|
|
529
|
+
|
|
530
|
+
### The palette in the tape
|
|
531
|
+
|
|
532
|
+
Since language version 2 a tape does not depend on the project's palette files: its `new` line
|
|
533
|
+
carries the palette, as it was when the asset was made, in a block of rows. One row per entry,
|
|
534
|
+
`<key> <name> #rrggbb [role=<role>]`, then one per ramp, `ramp <name> <entries...>`:
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
new sprite chest 24x24 palette=mossy-ruins wrap=off cell=24x24 bg=none <<
|
|
538
|
+
k ink #17181d
|
|
539
|
+
0 stone0 #272a30
|
|
540
|
+
...
|
|
541
|
+
ramp stone stone0 stone1 stone2 stone3 stone4 stone5
|
|
542
|
+
>>
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
You write `new sprite chest 24x24` (the project palette is read once, and written down), or give
|
|
546
|
+
the block yourself. A tape in language 1 (an older tape: its `new` line has no block) still
|
|
547
|
+
replays, reading the project palette as it did; a branch of such a tape stays in language 1. Its
|
|
548
|
+
header says which (`{"pclaw":"tape","lang":1}`), and `verify` refuses a header that does not match
|
|
549
|
+
the tape. So changing or saving a project palette (`palette save`) affects only assets made later.
|
|
550
|
+
|
|
551
|
+
### Underlays and snap
|
|
552
|
+
|
|
553
|
+
An **underlay** is a full-colour image under the art: a generated mockup, a sketch, a
|
|
554
|
+
photo of a drawing. It sits on a reference layer, stretched over a rectangle of the document, is
|
|
555
|
+
shown faded under transparent pixels (the `render` view, the watch view) and is never part of the
|
|
556
|
+
art or an export. Images are kept as content-addressed blobs (`blobs/<sha256>.png`).
|
|
557
|
+
|
|
558
|
+
- `underlay generate "<prompt>" [layout=<template.png>|<layer>] [image=1536x1024] [provider=openai|fake] [model=<m>] [quality=low|medium|high] [cap=<usd>] [layer=mockup] [at=x,y] [size=WxH]`
|
|
559
|
+
asks an image provider for one image. `layout=` is the flat template it should follow (a PNG in
|
|
560
|
+
the project such as `templates/mockup.png`, or a layer of the asset drawn in flat colours): it
|
|
561
|
+
is sent as the image to edit, and kept as a blob for snap's material match. `image=` is the size
|
|
562
|
+
to ask for (default: the one of 1024x1024, 1536x1024 and 1024x1536 nearest the document's
|
|
563
|
+
shape). The default provider is `openai` (model `gpt-image-1.5`, quality `medium`; project.json
|
|
564
|
+
`underlay` changes the defaults); `fake` draws an offline draft of the layout (tests and demos).
|
|
565
|
+
The key, the cost cap and the cost log are described in [mcp.md](mcp.md#underlays-and-the-openai-provider).
|
|
566
|
+
- `underlay import <file.png> [layout=<template.png>|<layer>] [layer=underlay] [at=x,y] [size=WxH]`
|
|
567
|
+
takes any PNG (relative to the project, or an absolute path).
|
|
568
|
+
- The size `underlay generate` asks for follows **the placed area** (`size=`, else the canvas):
|
|
569
|
+
the supported size nearest its shape (1536x1024 landscape, 1024x1536 portrait, 1024x1024
|
|
570
|
+
square), so a 16x32 placement on a 48x32 sheet asks for a portrait image. A layer layout is cut
|
|
571
|
+
to that area too.
|
|
572
|
+
- Both run through the server (the MCP `run` tool, the watch view's command line, `pixelclaw run`)
|
|
573
|
+
before the batch, and are written as **`underlay <layer> blob=<sha256> image=<W>x<H> at=<x,y> size=<W>x<H> [layout=<sha256>] [from="…"] [prompt="…" provider=… model=… quality=… cost=…]`**:
|
|
574
|
+
the tape holds only the image's hash, where it was placed and where it came from. The layer is
|
|
575
|
+
made at the bottom as a reference layer (or an empty reference layer of that name is reused).
|
|
576
|
+
Each such command goes on a line of its own.
|
|
577
|
+
- `snap [from=<underlay layer>] [into=draft] [fit=placement|free]` reads the underlay back into
|
|
578
|
+
pixel art on a **new draft layer** (on top): it fits the image's pixel grid. With
|
|
579
|
+
`fit=placement` (the default) the grid is the placement's: the pitch is the image's
|
|
580
|
+
size over the placed size, exactly one block per placed pixel, and only the offset is fitted
|
|
581
|
+
(the edge profile's phase at that pitch), so the draft lands on the placement pixel for pixel.
|
|
582
|
+
`fit=free` searches the pitch too (the edge profile's strongest period within ±15 % of the
|
|
583
|
+
placement's; the original rule), which can drift to a nearby period on a draft without a crisp grid.
|
|
584
|
+
Then
|
|
585
|
+
it takes each block's colour (the median of its middle), quantises it to the palette, and reports
|
|
586
|
+
**block R²** (how much of the image's colour the blocks explain) and, when the underlay has a
|
|
587
|
+
layout, the **material match** (the share of blocks nearest the mean colour of their own layout
|
|
588
|
+
colour's blocks). The line records the result:
|
|
589
|
+
`snap from=mockup into=draft at=0,0 grid=144x96 pitch=5.333x5.334 offset=0.66,0.65 r2=0.97 material=0.98 << rows >>`,
|
|
590
|
+
so replay never reads the image. Below R² 0.8 or material 0.85 the run warns: reroll the
|
|
591
|
+
underlay, or trace over it instead.
|
|
592
|
+
- `import <x,y> asset=<name> region=<x,y,WxH> [layer=<layer>]` copies a region of another asset (one
|
|
593
|
+
of its layers, or its flat art) onto your layer: a tile cut from a snapped scene. The line
|
|
594
|
+
records the pixels (`import 0,0 from="yard:draft 120,0,24x24" << rows >>`).
|
|
595
|
+
|
|
596
|
+
### Fonts
|
|
597
|
+
|
|
598
|
+
A **font** is a glyph sheet: one cell per character, in the order given.
|
|
599
|
+
|
|
600
|
+
- `new font <name> <W>x<H> [chars="ABC…"|32-126,160] [cols=16] [baseline=<row>] [line=<px>] [spacing=1]`:
|
|
601
|
+
`W`x`H` is a glyph cell; the canvas is `cols` cells across and as many rows as the characters
|
|
602
|
+
need. `chars=` is the characters themselves (quoted), or code points and ranges (decimal or
|
|
603
|
+
`U+hex`); the normal form writes code points and ranges in the given order
|
|
604
|
+
(`chars=65-90,48-57,32`). `baseline` is the cell row glyphs stand on (default the last), `line`
|
|
605
|
+
the line height (default the cell height + 1), `spacing` the pixels after each advance.
|
|
606
|
+
- `glyph <char> [advance=<n>|auto] [<< rows >>]` draws one glyph: the rows (palette keys, `.` or a
|
|
607
|
+
space for nothing) replace its cell from the top-left, on your layer and frame. Letters and
|
|
608
|
+
digits are written as themselves; anything else as `U+0021` (or quoted, `"!"`). A glyph's
|
|
609
|
+
advance is its ink width (the rightmost drawn column + 1) unless `advance=` sets it; an empty
|
|
610
|
+
glyph (a space) is half a cell unless set.
|
|
611
|
+
- `font set [baseline=] [line=] [spacing=]` changes the metrics.
|
|
612
|
+
- Any drawing command works on the sheet too (`paint @cell(3,1) …`).
|
|
613
|
+
- Exports: the **glyph strip** and **BMFont** (see [profiles.md](profiles.md#fonts)), with
|
|
614
|
+
`pixelclaw export <project> <font> --format strip|bmfont`, MCP `export(asset, format)`, or a
|
|
615
|
+
profile's `fonts`.
|
|
616
|
+
|
|
617
|
+
### Icons
|
|
618
|
+
|
|
619
|
+
`icon mode=tone|full|channel` sets how an icon is exported: **tone** art is recoloured
|
|
620
|
+
by the game (role entries, written through the profile's role colours), **full** art is drawn as
|
|
621
|
+
is (often a white silhouette), **channel** art is pure-RGB channel masks (every colour's channels
|
|
622
|
+
0 or 255, written exactly). Without it an icon is `tone` when its palette has roles, else `full`.
|
|
623
|
+
The `palette` check holds the art to its mode; `consistency` holds an icon set to one stroke
|
|
624
|
+
weight, box fill and optical centre ([checks.md](checks.md)).
|
|
625
|
+
|
|
626
|
+
### Palette edits
|
|
627
|
+
|
|
628
|
+
A document holds its own copy of its palette, taken from the project when `new` runs. `palette
|
|
629
|
+
set` and `palette add` edit that copy, as commands on the tape, so replaying the tape gives the
|
|
630
|
+
same colours even if the project's palette file changes later. Entries are never removed or
|
|
631
|
+
reordered (pixels store entry numbers); the names on the tape stay valid.
|
|
632
|
+
|
|
633
|
+
- `palette set <entry> hex=#rrggbb` changes an entry's colour. The entry may be named by its name
|
|
634
|
+
or its key; the tape records the name.
|
|
635
|
+
- `palette add [<name>] hex=#rrggbb [key=<k>]` appends an entry. Without a name it is called
|
|
636
|
+
`c<N>` (N = its position); without a key it gets the first free one (`0-9`, `a-z`, `A-Z`, then
|
|
637
|
+
punctuation). The normal form always records both.
|
|
638
|
+
- **Roles** (recolourable art): `palette add <name> role=<role>` and `palette set
|
|
639
|
+
<entry> role=<role>|none`. The roles are `black`, `white`, `light`, `dark` and `darkest`. A role
|
|
640
|
+
entry stands for a colour a theme gives it; its own `hex` (by default the grey theme's) is only
|
|
641
|
+
what it looks like without a theme. In role art (a palette with any role entry) the `palette`
|
|
642
|
+
check wants every pixel to be a role, and the `black` role counts as outline ink. Themes are for
|
|
643
|
+
looking only: the watch view's theme menu and the `recolour` and `theme=` views. Built-in themes:
|
|
644
|
+
`grey`, `moss`, `ember`, `tide`, `dusk`, `sand`; a project adds its own in `project.json`
|
|
645
|
+
(`"themes": { "lantern": { "light": "#f2d27a", "dark": "#b0702c", "darkest": "#4a2410" } }`).
|
|
646
|
+
|
|
647
|
+
`#` would start a comment after a space, so colours are written as `hex=#…`.
|
|
648
|
+
|
|
649
|
+
### Palette locking
|
|
650
|
+
|
|
651
|
+
`palette lock` locks this document's palette: `palette set`, `palette add` and `ramp` are refused
|
|
652
|
+
(`palette 'mossy-ruins' is locked (palette unlock to change it)`) until `palette unlock`. Colours
|
|
653
|
+
outside the palette are always refused (`c=#ff00ff is not in palette 'mossy-ruins'`), locked or
|
|
654
|
+
not. Importing images will quantise to a locked palette.
|
|
655
|
+
|
|
656
|
+
`palette save as=<name>` writes this document's palette, as it is at that line, to the project as
|
|
657
|
+
`palettes/<name>.json`, for other assets to use (`new ... palette=<name>`). It is refused if the
|
|
658
|
+
project already has a different palette of that name, because every tape made with that palette
|
|
659
|
+
would then replay differently; saving an identical one again is fine. The line stays on the tape
|
|
660
|
+
as a record; replay does not write the file again, and undoing it does not remove the file.
|
|
661
|
+
|
|
662
|
+
### Notes
|
|
663
|
+
|
|
664
|
+
`note 12,5 "too dark"` pins a note to a pixel; `note 8,0 12x10 "…"` to a region (inside the
|
|
665
|
+
canvas). Notes are for whoever reads the tape next: people pin them in the watch view, and the
|
|
666
|
+
agent reads them through `changes` (see [mcp.md](mcp.md)). The normal form adds the note's id,
|
|
667
|
+
`id=n1`, one past the highest id on the tape, so ids are never reused, even after an undo. Open
|
|
668
|
+
notes are part of the document's state (they appear in `asset.json` and the state hash) and show
|
|
669
|
+
on the canvas. `note done n1` closes one. Undoing a note's group removes it.
|
|
670
|
+
|
|
671
|
+
### Marks
|
|
672
|
+
|
|
673
|
+
`mark <name> ["text"]` names this point on the tape: `mark reviewed "blind review
|
|
674
|
+
g10: 5 findings fixed"`. Names are words (a-z, 0-9, `_`, `-`). Three set an asset's status
|
|
675
|
+
(see [pipeline.md](pipeline.md#statuses)): `reviewed` (the agent, after a blind review's findings
|
|
676
|
+
are dealt with), `approved` and `rejected` (a person, from the dashboard). Each mark keeps the
|
|
677
|
+
art's hash, so a later change to the pixels makes it stale. Marks are part of the state (written
|
|
678
|
+
only when there are any); undoing the group takes the mark away.
|
|
679
|
+
|
|
680
|
+
### Undo and redo
|
|
681
|
+
|
|
682
|
+
- `undo` takes back the latest group that is still in effect and is not itself an undo. The
|
|
683
|
+
chosen group is written into the line (`undo group=g4`).
|
|
684
|
+
- `undo group=g2` takes back any earlier group still in effect. Later groups stay.
|
|
685
|
+
- `undo by=human:chris` takes back that author's latest drawing group still in effect, whoever
|
|
686
|
+
drew after it. The watch view's Undo button (Ctrl/⌘+Z) writes this for the person using it.
|
|
687
|
+
- `redo` takes back an undo: the latest undo still in effect (`by=<author>`: that author's), as
|
|
688
|
+
long as nothing new was drawn after it (by that author). Drawing something new ends the redo
|
|
689
|
+
chain, as in any editor. `redo group=h5` takes back undo group `h5`. The line records the group:
|
|
690
|
+
`redo group=h5`. Undoing an undo group with `undo group=…` is the same thing.
|
|
691
|
+
- The group that created the document (with `new` or `fork`) cannot be undone. A batch cannot undo
|
|
692
|
+
its own group.
|
|
693
|
+
|
|
694
|
+
The agent's plain `undo` is unchanged: the latest group in effect, whoever wrote it.
|
|
695
|
+
|
|
696
|
+
### Forks
|
|
697
|
+
|
|
698
|
+
`fork <from> <to> [at=<n>]` starts asset `<to>` as a branch of `<from>` at command `<n>` (default:
|
|
699
|
+
its last). It must be the first command of a batch run on the new asset (in MCP:
|
|
700
|
+
`run(asset: "chest_b", commands: "fork chest chest_b at=20")`; in the watch view: time travel to a
|
|
701
|
+
line, then **Branch here**). The source is not touched.
|
|
702
|
+
|
|
703
|
+
#### The branching tape format
|
|
704
|
+
|
|
705
|
+
A branch's `tape.jsonl` is self-contained. It holds:
|
|
706
|
+
|
|
707
|
+
1. the source's entries 1..`at`, copied exactly (same `seq`, `by`, `at`, `group` and `cmd`), so
|
|
708
|
+
the branch keeps who drew what;
|
|
709
|
+
2. the fork line, numbered `at+1`, in the forking author's next group: `fork chest chest_b at=20`;
|
|
710
|
+
3. the branch's own commands after it.
|
|
711
|
+
|
|
712
|
+
(This example is a language 1 tape; in language 2 the `new` line also carries its palette block.)
|
|
713
|
+
|
|
714
|
+
```
|
|
715
|
+
{"pclaw":"tape","lang":1}
|
|
716
|
+
{"seq":1,"by":"claude","at":"…","group":"g1","cmd":"new sprite chest 12x12 palette=mossy-ruins wrap=off cell=12x12 bg=none"}
|
|
717
|
+
{"seq":2,"by":"claude","at":"…","group":"g1","cmd":"rect 1,3 10x8 c=soil1 fill"}
|
|
718
|
+
{"seq":3,"by":"claude","at":"…","group":"g1","cmd":"rect 1,3 10x8 c=ink outline"}
|
|
719
|
+
{"seq":4,"by":"human:chris","at":"…","group":"h1","cmd":"fork chest chest_b at=3"}
|
|
720
|
+
{"seq":5,"by":"human:chris","at":"…","group":"h2","cmd":"fill 3,5 c=moss2 mode=contiguous conn=4"}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Replay is the ordinary replay: the copied prefix rebuilds the source's document as it was at
|
|
724
|
+
`at`, and the fork line renames it to the branch. When replayed, the fork line checks that the
|
|
725
|
+
document is `<from>` and that `at` is the command just before it, so a tape whose prefix was
|
|
726
|
+
edited does not replay. The fork's group cannot be undone (it made this asset); the copied groups
|
|
727
|
+
can, as on any tape. The source's later history, or even its tape being started again, never
|
|
728
|
+
changes a branch. `asset.json` records the link: `"links": [{"fork": {"from": "chest", "at": 3}}]`.
|
|
729
|
+
|
|
730
|
+
## Views
|
|
731
|
+
|
|
732
|
+
`pixelclaw view <project> <asset> [render|grid|tile3x3|diff|game|recolour|anim|map|family|all]` (`all` is the
|
|
733
|
+
first three):
|
|
734
|
+
|
|
735
|
+
- **render**: a nearest-neighbour PNG. The default scale gives about 192 px on the long side
|
|
736
|
+
(8x for a 24 px tile). It has a faint pixel grid, stronger lines every 8 px, rulers labelled
|
|
737
|
+
every 8 px, magenta cell lines on sheets, and a grey checkerboard for transparent pixels.
|
|
738
|
+
`--region` shows part of the canvas, at the scale given, the rulers keeping the canvas's
|
|
739
|
+
coordinates.
|
|
740
|
+
- **grid**: the text index grid. There is one character per pixel (its key; `.` for transparent),
|
|
741
|
+
with column and row numbers and a legend of the keys used with pixel counts.
|
|
742
|
+
`--region` shows part of the canvas.
|
|
743
|
+
- **tile3x3**: the document repeated 3x3, at 1x and at 4x (or `--scale`) side by side, to show
|
|
744
|
+
seams and repetition.
|
|
745
|
+
|
|
746
|
+
A region is written the same way everywhere: `x,y WxH`, `x,y,WxH` or `x,y,W,H`, in
|
|
747
|
+
`--region`, MCP `view region=`, and `import … region=` (quote the form with a space:
|
|
748
|
+
`region="0,0 24x24"`).
|
|
749
|
+
- **diff**: before and after side by side at the render scale. Each changed pixel is outlined in
|
|
750
|
+
magenta on both, and open notes are outlined in yellow dashes. With more than 60 changed pixels a
|
|
751
|
+
third panel shows them as a map (the after image dimmed, changes solid magenta). The text lists
|
|
752
|
+
up to 60 changes as `x,y from→to` by entry name. By default it shows what the latest group
|
|
753
|
+
changed; the MCP `view` takes `since=<n>` and `changes` uses the agent's read position. On
|
|
754
|
+
documents with frames it compares every frame: one row per frame that changed (numbered at the
|
|
755
|
+
left), and the text says which (`f2 12,5 ink→wood2`).
|
|
756
|
+
- **game**: the asset on its usual ground tile (repeated under it) beside the rest of its set,
|
|
757
|
+
at 1x (real size) and again at the engine's draw scale (`style.drawScale`, default 2, or 1 when the project's grid is 48 px or more; at 1 the scene is shown once). The
|
|
758
|
+
ground is `--ground`, else the style guide's (`style.ground`, or per asset), else the first
|
|
759
|
+
tile; the set is `--with` (repeatable), else every other asset of the same kind and size that
|
|
760
|
+
does not wrap, leaving out branches of the same asset. A sprite with views shows its other
|
|
761
|
+
directions (each view's first frame) beside it first.
|
|
762
|
+
- **anim**: the contact sheet: one row per sequence (tags, views, or all frames), each
|
|
763
|
+
frame with its number and ticks above it, the frame before it in the sequence tinted red under
|
|
764
|
+
it (the onion skin), the pivot and ground line in cyan, and under it the share of pixels changed
|
|
765
|
+
from the frame before (`2>3 17%`; under the first frame of a cycle, the loop seam). The text gives
|
|
766
|
+
each sequence's frames, ticks, loop time at the project's tick rate and every frame-to-frame
|
|
767
|
+
change. It also writes `<asset>.anim.gif`: every sequence side by side, looping at the game's
|
|
768
|
+
speed (tiles 3x3).
|
|
769
|
+
- **recolour**: role art under several preview themes side by side (`--themes a,b,c`, default the
|
|
770
|
+
first six), each with a strip of its role colours.
|
|
771
|
+
- **map**: a sample map built with a family's scheme: a random island of
|
|
772
|
+
`--size CxR` tiles (default 12x10) from `--seed N` (default 1), each tile its variant, at the
|
|
773
|
+
game's draw scale (`--scale`, default 2). The text gives the seam score over the map (the same
|
|
774
|
+
metric as a tile's, over the composed map) and how often each variant appears. Overlays given
|
|
775
|
+
with `--with` play over it; when the family's frames or the overlays move it also writes
|
|
776
|
+
`<asset>.map.gif` (one loop of everything, at most four seconds). A tile's map is the tile
|
|
777
|
+
repeated; an overlay's is over the first family (or `--ground`).
|
|
778
|
+
- **family**: every variant of a family in the middle of the 3x3 map that resolves to it (the
|
|
779
|
+
neighbours dimmed), labelled (pieces in cyan, detached variants in amber); the text says where
|
|
780
|
+
every cell is on the sheet.
|
|
781
|
+
|
|
782
|
+
`--marks failing` (or check names: `--marks orphans,jaggies`) circles the pixels those checks flag
|
|
783
|
+
in magenta, on `render`, `tile3x3` (the middle copy of the enlarged one) and `game`. `--theme
|
|
784
|
+
<name>` shows role entries under a theme. See [checks.md](checks.md) for the checks.
|
|
785
|
+
|
|
786
|
+
Views show the art layers of one frame (`--frame N`, default 1 in the CLI; the agent's frame in
|
|
787
|
+
MCP), with visible reference layers faded underneath in `render`. Views are written to
|
|
788
|
+
`<project>/views/<asset>.<mode>.png` (or `.txt`), or to `--out <dir>`.
|
|
789
|
+
|
|
790
|
+
## The tape
|
|
791
|
+
|
|
792
|
+
`assets/<name>/tape.jsonl` starts with a version header, then one entry per command:
|
|
793
|
+
|
|
794
|
+
```
|
|
795
|
+
{"pclaw":"tape","lang":2}
|
|
796
|
+
{"seq":1,"by":"claude","at":"2026-10-27T12:00:00Z","group":"g1","cmd":"new tile floor 24x24 palette=mossy-ruins wrap=on cell=24x24 bg=stone1 <<\n k ink #17181d\n ...\n>>"}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
`by` is the author (the CLI's `--by`, default `claude`; `human:<name>` from the watch view), and
|
|
800
|
+
`at` is stamped by the writer. Replay ignores both, except that `undo by=` and `redo by=` are
|
|
801
|
+
resolved by author when first written (the tape records the group they chose).
|
|
802
|
+
`pixelclaw verify` checks five things:
|
|
803
|
+
|
|
804
|
+
- every line replays and is still in normal form;
|
|
805
|
+
- two replays give the same bytes;
|
|
806
|
+
- the result matches the state hash in `asset.json` (a hash written by an early version, in the older state
|
|
807
|
+
format, is accepted until the asset is saved again);
|
|
808
|
+
- the result re-renders to the committed `preview.png`;
|
|
809
|
+
- every checkpoint matches the replay at its point, byte for byte, and none is missing.
|
|
810
|
+
|
|
811
|
+
### Checkpoints
|
|
812
|
+
|
|
813
|
+
Every 200 commands the writer saves `checkpoints/NNNNNN.gwc`: the document after the first N
|
|
814
|
+
commands. The file is a header line, `PCLAWCKPT 1 <N> <sha256 of those N tape lines>`, then the
|
|
815
|
+
document's state bytes (the same bytes the state hash in `asset.json` is taken of: palette, layers,
|
|
816
|
+
frames, every cel, each author's context, notes, stamps and macros). Loading an asset starts from
|
|
817
|
+
the newest checkpoint whose tape lines still match and replays only the rest. Starting a tape
|
|
818
|
+
again removes its checkpoints.
|
|
819
|
+
|
|
820
|
+
### Import and densities
|
|
821
|
+
|
|
822
|
+
| Command | Normal form | Notes |
|
|
823
|
+
|---|---|---|
|
|
824
|
+
| `import <x,y> [from="<file>"] [blob=<sha256>] << rows >>` | `import 0,0 from="sheet.png" blob=3f… <<` rows `>>` | An imported image's pixels on your layer and frame: each row is palette keys, `.` transparent; the rectangle is replaced. `pixelclaw import` and MCP `import` write these (see [profiles.md](profiles.md#import)); the line carries the pixels, so the tape replays without the file. |
|
|
825
|
+
| `scale <n>` | `scale 2` | The whole document n times larger (2..8), nearest neighbour: every layer, frame and stamp, the cell grid (a footprint keeps its cells), the pivot, the ground line and notes. Selections and symmetry are dropped. Not on families or maps. A higher density starts this way. |
|
|
826
|
+
| `density of=<asset> d=<n>` / `density off` | `density of=house d=2` | This asset is `<asset>` at n times its size: the `density` check reduces it and compares, the density view shows the levels, and exports write it as that level. |
|
|
827
|
+
|
|
828
|
+
`new family <name> <T>x<T> scheme=<s>` with a scheme of the project's own (`schemes/*.json`)
|
|
829
|
+
writes it into the line as `def="<its JSON>"`, like the palette, so the tape replays without the
|
|
830
|
+
file. A fillet scheme (`blob47-round`) takes `family band … fillet=<px>`: how much wider every
|
|
831
|
+
inner corner turns (kept by later `family band` lines unless given again).
|
|
832
|
+
|
|
833
|
+
## Seeds
|
|
834
|
+
|
|
835
|
+
Commands that use randomness (`noise`, `scatter`, `voronoi`, `bricks`, `pebbles`, `tufts`,
|
|
836
|
+
`map gen`, `family coverage`, `family band … jitter=`, and `call` when its macro uses them) take `seed=<n>`. The MCP
|
|
837
|
+
`seeds` tool (and `pixelclaw seeds`) tries one such line with several seeds as dry runs and ranks
|
|
838
|
+
them by the checks; `run` with `dry=true` (`pixelclaw run --dry`) tries any batch and keeps
|
|
839
|
+
nothing. If it is left out, the normaliser picks one from the command's tape
|
|
840
|
+
position and writes it into the logged line, so a replay never depends on a default.
|