Regions & Shapes
Regions define the visual structure of a sprite using geometric primitives. Each region is a named area that maps to a color token from the palette.
Overview
{
type: "sprite",
name: "hero",
size: [16, 16],
palette: "hero",
regions: {
outline: { stroke: [0, 0, 16, 16] },
skin: { fill: "inside(outline)", y: [4, 12] },
eye: { points: [[5, 6]], symmetric: "x" }
}
}
Unlike the legacy grid format, regions scale with semantic complexity rather than pixel count. A 64x64 sprite takes the same space as an 8x8 sprite if they have similar structure.
Shape Primitives
Points
Individual pixels at specific coordinates.
eye: { points: [[2, 3], [5, 3]] }
Line
Bresenham line between points.
mouth: { line: [[3, 6], [5, 6]] }
// Multiple segments
crack: { line: [[0, 0], [2, 3], [4, 2]] }
Optional: thickness (default: 1)
Rectangle
Filled rectangle.
body: { rect: [2, 4, 12, 8] } // [x, y, width, height]
Optional: round (corner radius)
Stroke
Rectangle outline (unfilled).
outline: { stroke: [0, 0, 16, 16] }
Optional: thickness, round
Ellipse
Filled ellipse.
head: { ellipse: [8, 4, 6, 4] } // [cx, cy, rx, ry]
Circle
Shorthand for equal-radius ellipse.
dot: { circle: [4, 4, 2] } // [cx, cy, r]
Polygon
Filled polygon from vertices. Winding order doesn't matter - clockwise or counter-clockwise produces identical results.
hair: {
polygon: [[4, 0], [12, 0], [14, 4], [2, 4]]
}
Tip: Keep polygons simple (3-5 vertices). Complex polygons with 6+ vertices may cause fill artifacts. Use
unionof simpler shapes instead.
Path
SVG-lite path syntax.
complex: {
path: "M2,0 L6,0 L8,2 L8,6 L6,8 L2,8 L0,6 L0,2 Z"
}
Supported commands:
M x,y- Move toL x,y- Line toH x- Horizontal line toV y- Vertical line toZ- Close path
Curves (C, Q, A) are not supported as they don't make sense for pixel art.
Fill
Flood fill inside a boundary.
skin: { fill: "inside(outline)" }
Optional: seed: [x, y] (starting point, auto-detected if omitted)
Compound Operations
Union
Combine multiple shapes.
hair: {
union: [
{ rect: [2, 0, 12, 2] },
{ rect: [0, 2, 16, 2] }
]
}
Subtract
Remove shapes from a base.
face: {
base: { rect: [2, 4, 12, 8] },
subtract: [
{ points: [[5, 6], [10, 6]] } // eye holes
]
}
Or using token references:
skin: {
fill: "inside(outline)",
except: ["eye", "mouth"]
}
Intersect
Keep only overlapping area.
visor: {
intersect: [
{ rect: [2, 4, 12, 4] },
{ fill: "inside(helmet)" }
]
}
Modifiers
Symmetry
Auto-mirror across axis.
eye: {
points: [[4, 6]],
symmetric: "x" // mirrors to [11, 6] for 16-wide sprite
}
Values:
"x"- Horizontal mirror (around canvas center)"y"- Vertical mirror (around canvas center)"xy"- Both axes (4-way symmetry)8- Mirror around specific x-coordinate
Range Constraints
Limit region to specific rows or columns.
hair: {
fill: "inside(outline)",
y: [0, 4] // rows 0-4 inclusive
}
"left-arm": {
fill: "inside(outline)",
x: [0, 8] // columns 0-8 inclusive
}
Containment Validation
Validate that region stays within another.
pupil: {
points: [[4, 6]],
within: "eye" // compiler validates this
}
Note: within is a validation constraint checked after all regions are resolved. It's distinct from fill: "inside(X)" which is a pixel-affecting operation.
Adjacency Validation
Ensure region touches another.
shadow: {
fill: "inside(outline)",
"adjacent-to": "skin",
y: [10, 14]
}
Z-Order
Explicit render order (default: definition order).
detail: {
points: [[8, 8]],
z: 100 // renders on top
}
Transform Modifiers
Repeat
Tile a shape.
bricks: {
rect: [0, 0, 4, 2],
repeat: [8, 16],
spacing: [1, 1],
"offset-alternate": true
}
Geometric Transform
Apply rotation, translation, scale.
sword: {
line: [[0, 0], [0, 8]],
transform: "rotate(45deg) translate(12, 4)"
}
Supported transforms:
translate(x, y)rotate(angle)- supportsdegorradscale(x, y)orscale(n)flip-x,flip-y
Jitter
Controlled randomness.
grass: {
points: [[0, 15], [4, 15], [8, 15], [12, 15]],
jitter: { y: [-2, 0] },
seed: 42
}
Auto-Generation
Auto-Outline
Generate outline around a region.
outline: {
"auto-outline": "body",
thickness: 1
}
Auto-Shadow
Generate drop shadow.
shadow: {
"auto-shadow": "body",
offset: [1, 1]
}
Background
Shorthand to fill all unoccupied pixels.
_: "background"
Region Resolution Order
Regions are processed in two passes:
-
Shape resolution (definition order): Each region's pixels are computed. Pixel-affecting operations (
fill: "inside(X)",except: [X],auto-outline: X) require X to be defined earlier. -
Validation (after all resolved): Constraints (
within,adjacent-to) are checked. These can reference any region.
This enables streaming while catching errors:
regions: {
outline: { stroke: [0, 0, 16, 16] }, // defined first
skin: { fill: "inside(outline)" }, // OK: outline exists
eye: { rect: [5, 6, 2, 2] },
pupil: { points: [[6, 7]], within: "eye" } // OK: within is validation-only
}
Error (forward reference in pixel-affecting operation):
regions: {
skin: { fill: "inside(outline)" }, // ERROR: outline not yet defined
outline: { stroke: [0, 0, 16, 16] }
}
Visual Reference
Here's how common shapes map to coordinates:
CIRCLE: circle: [cx, cy, r]
Example: circle: [16, 12, 8]
0 4 8 12 16 20 24 28 32
4 │ ●●●●●●● │ ← radius 8
8 │ ●● ●● │
12 │ ● (16,12) ● │ ← center
16 │ ●● ●● │
20 │ ●●●●●●● │
RECTANGLE: rect: [x, y, w, h]
Example: rect: [8, 16, 16, 8]
0 4 8 12 16 20 24 28 32
16 │ ████████████████ │ ← y=16 (top)
20 │ ████████████████ │
24 │ ████████████████ │ ← y=24 (top + height)
↑ ↑
x=8 x=24 (x + width)
POLYGON (Triangle): polygon: [[16,4], [4,28], [28,28]]
0 4 8 12 16 20 24 28 32
4 │ ▲ (16,4) │ ← tip
12 │ █████ │
20 │ █████████ │
28 │ (4,28) █████████████ (28,28) │ ← base
COMBINED: Skull silhouette (circle + rect + polygon)
0 4 8 12 16 20 24 28 32
4 │ ●●●●●●● │
8 │ ●●●●●●●●●●● │ ← circle (cranium)
12 │ ●●●●●●●●●●●●● │
16 │ ●●●●●●●●●●● │
20 │ ████████████████ │
24 │ ████████████████ │ ← rect (mid-face)
28 │ ████████████████ │
32 │ ██████████████ │
36 │ ████████████ │ ← polygon (jaw taper)
Design tip: Start with 2-3 basic shapes (circle + rect + polygon) for silhouettes. Get proportions right before adding detail.