One prompt, eleven agents, no human art direction. Click any panel to zoom.
Given verbatim to all 11 arms, uncorrected — shurbbery for shrubbery, pixelart for the skill's real name pixel-art, and the mangled clause "We see what they, see":
Use the pixelart skill to create an illustration ( wide shot) of a party of adventurers (3),seen from behind, partly hidden in forest, looking down into clearing down the hill. We see what they, see a ruined and falling apart castle with goblin patrols among forest and shurbbery. You must perform at least three iterations.
pixel-art — procedural drawing code on an HTML canvas, never image generation.
Draws into a small offscreen logical buffer (~256px wide), then upscales hard to the display canvas with nearest-neighbour — no smoothing, no blur, integer coordinates only, palette held to 8-24 colours with ordered Bayer 4x4 dithering applied only to the transition band between two ramp steps, never across a whole surface.
Authoring runs write, render, look, diagnose, revise — the skill forbids emitting DONE without a render the agent has actually viewed that turn.
One haiku arm, plus sonnet-5 and opus-5 each at low, medium, high, xhigh and max effort.
Effort is set in agent frontmatter, not at the call site. Every arm worked in its own directory and never saw another arm's output.
The tok and tools figures are cost, not quality. iters, palette, res and revs are self-reported by each arm in its own PROFILE.json; tok, tools and time are measured independently by the harness.
The pixel-art skill supplies the authoring discipline and the checklist each arm iterates against. rodney drives Chrome headlessly to load the scene and take the screenshot each iteration reads before revising. A local static server serves the scene files, since `file://` renders as a static no-JS snapshot. All eleven agents were dispatched in parallel from a single message, each in its own directory, none seeing another's output.
---
name: pixel-art
description: Use when creating procedural pixel art or dithered retro scenes via code. Triggers on pixel art, retro art, dithered rendering, low-res scene, 8-bit aesthetic, sprite art, colour-cycled animation, Mark Ferrari style, procedural art, canvas pixel rendering.
---
# Dithered Pixel Art via Code
You author pixel art as **procedural drawing code** on an HTML Canvas, not as image files.
No image generation. You write code that computes pixels, render it, look at the
result, and revise. The loop is the method — a scene you have not looked at is
not finished.
---
## 0. Harness setup (do this first)
Every scene is a self-contained HTML file with a `<canvas>`. The Browser Preview
is your render target and inspection tool. If any piece below is missing,
build it before drawing.
### File structure
```
scene-id.html # one file per scene
```
Each HTML file:
1. Creates an **offscreen canvas** at logical resolution (~256 px wide).
2. Draws into that buffer with `imageSmoothingEnabled = false`.
3. Upscales to a display `<canvas>` with nearest-neighbour (hard pixel edges).
4. Reads URL params for state variants: `?flag=key&t=0.5`.
### Render and inspect
| Action | Tool |
|---|---|
| Serve the file | `preview_start` with the HTML file |
| View at display size | `screenshot` |
| View at 1x logical / squint | `zoom` on the canvas region |
| Force a state variant | `navigate` with query params (`?door=open&t=1.3`) |
| Check pixel hash | `javascript_tool`: `canvas.toDataURL()` and compare |
| Inspect console | `read_console_messages` |
### Minimum viable template
```html
<!DOCTYPE html>
<html>
<head><style>html,body{margin:0;background:#111;display:flex;justify-content:center;align-items:center;height:100vh}canvas{image-rendering:pixelated}</style></head>
<body>
<canvas id="c"></canvas>
<script>
const W = 256, H = 192, SCALE = 3;
const display = document.getElementById('c');
display.width = W * SCALE; display.height = H * SCALE;
const dCtx = display.getContext('2d');
dCtx.imageSmoothingEnabled = false;
const buf = new OffscreenCanvas(W, H);
const ctx = buf.getContext('2d');
const params = new URLSearchParams(location.search);
const t = parseFloat(params.get('t') ?? '0');
function draw(time) {
// --- drawing code goes here ---
ctx.fillStyle = '#0a0a12';
ctx.fillRect(0, 0, W, H);
// --- end drawing code ---
dCtx.clearRect(0, 0, display.width, display.height);
dCtx.drawImage(buf, 0, 0, display.width, display.height);
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>
</body>
</html>
```
### Pixel-hash baseline (refactor safety)
Before restructuring drawing code:
```js
// capture via javascript_tool in the browser
const hash = document.getElementById('c').toDataURL();
```
Compare before and after. Must be identical — if not, you changed output.
### If you cannot screenshot, stop
Do not produce scenes you cannot inspect. Say so and stop.
---
## 1. Technical constraints (non-negotiable)
- **Render into a small offscreen buffer (~256 px wide), then upscale hard**
with nearest-neighbour / `imageSmoothingEnabled = false`. Working at full
resolution and "trying to look pixelly" does not work.
- **Integer coordinates only.** Snap every x/y/w/h. Sub-pixel positions blur.
- **No blur, no `shadowBlur`, no gaussian glow, no additive neon.** Glow is
faked with dithered concentric value steps.
- **No anti-aliasing.** Embrace the stair-step. Clean diagonals come from
*consistent* pixel runs (1:1, 2:1, 3:1), not blending.
- **Binary alpha only.** Every pixel is one palette colour, fully opaque.
- Total palette: **8-24 colours** for the whole scene. Count them. Literally.
---
## 2. Authoring order — strict passes
Do these in order. Do not polish pass 5 while pass 2 is unresolved. When a scene
"won't come together," back up a pass; do not add detail.
1. **Silhouette.** Identifiable from black shape alone, in one second.
2. **Three value masses.** Dark / mid / light. Nothing else yet.
3. **One key light.** Single dominant source. Two competing light pools = no
focal point. No in-world source? Moon is the key, scene is cool.
4. **Texture**, oriented along each surface's form.
5. **Selective detail**, concentrated at the focal point only.
### Colour rules
- Each material: **3-5 step ramp that rotates hue** — shadows bend blue/violet,
highlights bend yellow/orange. `#333->#999` grey is the #1 tell of programmer art.
- **Share ramps across materials.** Wall's darkest = floor's shadow = prop's outline.
- **Never pure black or pure white inside the scene.** Darkest shadow keeps a
hue (`#070708`). Brightest stays under ~90%.
- Distance = lower contrast + cooler + lighter. Far treeline is near-flat silhouette.
### Dithering rules
- **Ordered Bayer 4x4 only.** Error-diffusion reads as a compression artifact.
- **Dither the transition band only** — the middle third where two ramp steps
meet. Pattern: solid step -> dithered band -> solid step.
- 100%-dithered surface = **dither soup** (reads as static). 0%-dithered = flat vector. Both fail.
- **Texture in clusters of 2+ pixels.** A lone stray pixel reads as a dead pixel.
---
## 3. The inspection loop
This produces quality. Everything above is what to try; this is how you verify.
```
1. WRITE one pass of drawing code
2. RENDER screenshot at display size
3. LOOK actually view the screenshot
4. DIAGNOSE against the checklist, in writing, before touching code
5. REVISE the single worst problem
repeat until checklist passes
```
### Rules
- **Step 3 is not optional and cannot be inferred.** Screenshot and view. Code
that "should" look right routinely does not.
- **Step 4 before step 5.** Write down what is wrong first. Editing while
diagnosing produces thrash.
- **Fix one thing per iteration.** Multi-fix passes hide which change helped.
- **Never say "done" without a render in the current turn.**
- Expect **3-6 iterations** for a new scene. One-shot = red flag.
### Three viewing modes — check all three
| View | How | Catches |
|---|---|---|
| **Display size** | `screenshot` | Detail legibility, texture as noise, jaggies |
| **Squint / 32 px** | `zoom` on a small region or downscale in code | Value structure — uniform mud means no texture saves it |
| **1x logical** | `zoom` on the canvas at native buffer size | Orphan pixels, sub-pixel leaks, banding |
If a detail only reads when zoomed in, **it does not exist.** Delete it.
### Checklist — every item must pass
**Value & composition**
- [ ] Squints to **three readable value masses**
- [ ] Exactly **one brightest spot**, at the intended focal point
- [ ] Landmark identifiable **by silhouette alone**
- [ ] Depth reads as ~3 layers + dark foreground frame
- [ ] **No tangents** — no two silhouettes kissing edge-to-edge
**Pixel discipline**
- [ ] Chunky pixels; no blur anywhere
- [ ] Dithered **transitions**, not full-surface dither, not hard banding
- [ ] No orphan single pixels
- [ ] Diagonals use consistent runs
**Colour**
- [ ] Palette counted and <= 24
- [ ] Every ramp shifts hue; **no straight greys**
- [ ] No pure black or pure white inside the scene
- [ ] Two steps within ~8% value at same hue -> merge into one
**Detail & texture**
- [ ] Texture oriented **with** each surface's form
- [ ] Detail concentrated at focal; background near-flat
- [ ] Enclosed spaces read as a **perspective box**, not gradient with noise
**State & motion**
- [ ] Every state branch rendered and viewed
- [ ] Two states **visibly different at a glance**
- [ ] 1-2 elements moving, not everything
### Failure -> cause lookup
| What you see | Cause | Fix |
|---|---|---|
| Kid's drawing | Flat fills, no texture, uniform outlines | Texture every surface; sel-out edges |
| Modern indie, not retro | Too many hues, too saturated | Cut palette; desaturate; add dither |
| Muddy / no focal point | Value masses not separated; even detail | Back up to pass 2; strip edge detail |
| Static / TV snow | Dither soup or random hash water | Dither transitions only; water = flow streaks |
| Blurry | Smoothing on, or fractional coords | `imageSmoothingEnabled=false`; round everything |
| Programmer art | Straight value-only ramps | Rotate hue across every ramp |
| Busy but cheap | Detail distributed evenly | Detail is a spotlight — remove from background |
| Flat, no depth | One value per plane, no atmosphere | Distance -> lower contrast, cooler, lighter |
| Thick blurry curve edge | Banding — highlight parallel to border | Vary run lengths, offset, or delete |
---
## 4. Animation
Animate by **moving values through fixed pixels**, not by moving geometry
(Mark Ferrari colour cycling).
- **8-12 fps.** Not 60. Choppiness is intentional.
- Loops of **2-6 frames**, seamless, non-narrative.
- Sub-pixel motion: **brightness shift** — fade a pixel up as its
neighbour fades down.
- **1-2 moving elements per scene.** Everything moving = screensaver.
| Element | Technique |
|---|---|
| Stars | Toggle 1 px on/off via `abs(sin(t*2+i)) > threshold` |
| Water / waterfall | Fixed shape, highlight streak phase scrolls along flow |
| Flame | Cycle 2-3 values through a fixed cluster; flutter glow peak |
| Rain | 1x2 streaks falling on fixed x columns |
| Sway | Integer-rounded `sin(t*w)` offset |
| Creature idle | 2 frames beats a static sprite |
**Inspect motion by rendering at 3+ values of `t`** (`?t=0`, `?t=1.3`, `?t=2.7`)
and viewing each. A still at t=0 tells you nothing about whether the loop reads.
### Frame-limiting pattern
```js
const FPS = 10;
const frameDur = 1000 / FPS;
let last = 0;
function draw(now) {
requestAnimationFrame(draw);
if (now - last < frameDur) return;
last = now;
// ... drawing code ...
}
```
---
## 5. Fidelity rule
If the scene depicts something with an authoritative description:
- **Atmosphere is free.** Weather, time of day, fog, mood, palette.
- **Content-bearing elements are not.** A lit window implies an occupant. A fire
implies a light source. Do not invent objects a viewer would reason about.
- When unsure, re-read the source description and render what's actually there.
---
## 6. Refactor safety
Before restructuring drawing code:
1. Capture pixel-hash baseline across all scenes and state variants.
2. Refactor.
3. Re-hash. **Must be byte-identical.** If not, you changed output.
---
## 7. Anti-patterns — reject on sight
- Declaring a scene done without a render **in the current turn**
- Describing what the image looks like without screenshotting it
- Glowing vector outlines, neon strokes, additive glow
- Flat filled silhouettes with no texture
- Full-res render + soft blur to fake "pixelly"
- `imageSmoothingEnabled` left true
- Fractional coordinates
- Straight grey / value-only ramps
- 100%-dithered surfaces, or lone noise pixels
- Even detail everywhere
- Water as per-pixel random hash
- A key prop as a glowing blob instead of silhouette + 2-3 features
- A floor opening as a flat black rectangle
- Fixing five things in one iteration
- Dropping a state branch during rework
---
## 8. Reporting format
Each iteration, report:
```
ITERATION n
Rendered: <file(s)>, states: <default | flag=true | t=0.0,1.3,2.7>
Observed: <what you actually see - 2-4 problems, worst first>
Checklist: <failing items>
Changing: <the one fix this iteration>
```
When finished:
```
DONE
Final render: <file>
Checklist: all pass
Iterations: n
Palette count: k
States verified: <list>
```
Do not emit `DONE` without a render you have viewed this turn.