Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Iterate on the visual identity of a top-down pixel-art decoration (sprite + layout integration) in pixtuoid. Use when redesigning an existing decoration (pantry, lounge, meeting room, cubicle decor) or adding a new one. Captures the rebuild trap, the visual-verification loop, resolution constraints, sprite-format pitfalls, and the layout-integration checklist that we learned the hard way during the pantry beautify session.
.claude/skills/ivanwng97-beautify-decoration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-19 | ✗→✓ | ▲ Improved | 142% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 126% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 230% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 169% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 95% | 0% |
A repo-specific iteration loop for visually redesigning a decoration in pixtuoid. Follow this when the user says "beautify X" or "make Y look better" — it short-circuits several rebuild traps and visual-design dead ends that aren't obvious from the codebase alone.
1. Edit sprite OR layout
↓
2. cargo build --release --example snapshot
↓
3. ./target/release/examples/snapshot --cols 192 --rows 80 /tmp/snap.png
↓
4. .venv/bin/python3 scripts/crop-snapshot.py /tmp/snap.png --scale 3 -q <quadrant>
(or skip the quadrant guessing: snapshot --crop-furniture pantry|couch|vending|
printer|meeting|sofa|chair|island|snackshelf|desk OR --crop-agent <label> renders a 40x24-cell window
already centered on the target — no Python step)
↓
5. Read the cropped PNG → self-critique → back to step 1
↓
6. When happy, send to user with SendUserFile and short caption
↓
7. cargo build --release --workspace ← rebuild the LIVE binary too
↓
8. Commit with iteration history (which designs were tried, why rejected)The user is the final judge of "does it look like a fridge / coffee machine / etc." — but you should self-critique before sending. Three iterations of self-critique before bothering the user.
Step 7 is mandatory. cargo build --release --example snapshot does NOT rebuild the main binary. Users testing with ./target/release/pixtuoid run won't see sprite changes until the workspace is rebuilt. Forgetting this step is how "I changed the sprite but nothing happened in the live TUI" bugs get filed.
Step 8 is mandatory. Commit messages for sprite changes must include the iteration count and a one-line rationale for each rejected attempt. Future editors need to know which alternatives were explored — otherwise they'll re-try the same dead-end designs (the seated_sleeping sprite went through 4 iterations before reading correctly at scale).
cargo build --release --workspace does not rebuild examples. Use cargo build --release --example snapshot when iterating on examples/snapshot.include_str! in crates/pixtuoid-scene/src/embedded_pack.rs bakes sprite files at compile time. A build.rs exists at crates/pixtuoid-scene/build.rs that emits rerun-if-changed for every .sprite and pack.toml — so a sprite edit DOES trigger a rebuild now. If you added a new asset and edits still aren't being picked up, check that build.rs is matching its extension.strings target/release/examples/snapshot | grep "<some unique string from your sprite>".examples/snapshot defaults to 192×80 cells → buffer 192×160. Several layouts (pantry, corridor appliances) have conditional variants based on room dimensions. Corridor items (vending machine, printer) only appear when walkway_h ≥ 9–10. Use the default --cols 192 --rows 80 to see everything.
Pantry-specific threshold: pantry_room.width >= 36 triggers the 32×10 sprite; below that, the 20×8 pantry_small.sprite is used. Threshold lives in crates/pixtuoid-scene/src/layout/compute.rs.
The full PNG is too big to grok at a glance and too small at thumbnail. Crop the relevant quadrant with PIL:
pythonfrom PIL import Image img = Image.open('/tmp/snap.png') w, h = img.size # Pantry is bottom-left quadrant; adjust ratios for other zones: # meeting: (0, 0, 0.30*w, 0.45*h) # pantry: (0, 0.49*h, 0.30*w, h) # cubicle: (0.30*w, 0, w, 0.55*h) # lounge: pre-2026 retired; merged into cubicle band crop = img.crop((0, int(h*0.49), int(w*0.30), h)) crop = crop.resize((crop.width*2, crop.height*2), Image.NEAREST) crop.save('/tmp/crop.png')
Then inspect the cropped PNG with the agent's image-viewing tool.
PIL is available system-wide (installed via pip3 install --user --break-system-packages Pillow). If a fresh environment misses it, install once.
Symptoms of weak identity:
.): the wall color shows through, weakening the silhouette. Use a solid fill color for appliances.M-bodied items reads as "row of dark boxes." Give each appliance a distinct base color (e.g., w white fridge against M dark coffee machine + M dark microwave with q glass).b is dark and gets dim. Space them out or use c + r..sprite file must have exactly the same number of space-separated cells. Off-by-one is the most common bug.awk '/^@/{next}/^#/{next}NF{print NR": "NF}' crates/pixtuoid-scene/sprites/default/foo.sprite — all NF values must match.awk '/^@/{next}/^#/{next}NF{for(i=1;i<=NF;i++)printf "%s",$i;print " ["NF"]"}' foo.sprite.embedded_pack.rs header comment).crates/pixtuoid-scene/sprites/default/pack.toml [palette] section.When a sprite changes size:
furniture_def(Furniture) geometry table in crates/pixtuoid-scene/src/layout/decor.rs — the single source of truth for footprint + visual, read by mask::build_walkable_mask (waypoints via approach::obstacle_footprint), approach.rs, and the z-sort. Do NOT hardcode a (w, h) at the mask stamp site; it would diverge from the table that stand_point/approach and render-centering read.FurnitureDef row via furniture_def(kind.furniture()).footprint, not an inline literal — so the same table edit covers it.cargo test -p pixtuoid-scene — the walkable_is_one_connected_region test (lives in layout/placement_sweep.rs) catches mask/sprite mismatches by sweeping buffer sizes × seeds and asserting every walkable pixel is reachable from the door threshold; narrow_band_connectivity_boundary_scan re-runs the same assert at the step-1 widths that discrete grid skips.SWEEP_SIZES starts at 34×60), the sprite is too big for that pantry. Add a _small variant + conditional pick (see PantryRoom::counter_size / SceneLayout::pantry_counter_size() for the pattern).crates/pixtuoid-scene/sprites/default/pack.toml and embedded_pack.rs to include both foo.sprite and foo_small.sprite if you added a variant../target/release/pixtuoid run uses the main binary. examples/snapshot uses its own binary. Both need cargo build --release --example snapshot (or cargo build --release --workspace --example snapshot) when iterating on snapshot — and cargo build --release is fine for the live TUI binary.
You must run this checklist explicitly before each SendUserFile in a beautify loop. State the result of each row in the message (✅/⚠️/❌). Fix any ❌ before sending; if you ship a ⚠️, call it out so the user knows the trade-off.
| Check | What it means | |---|---| | Stranger-ID | If a stranger saw this with no context, would they identify each new element as the intended thing? Name each element explicitly. | | Visually differs | Diff is noticeable, not a sub-pixel tweak. If hash-identical to last attempt, you didn't actually rebuild. | | Subzone width | Each new sub-element ≥ 5 display cells wide (horizontal cells = buffer px; vertical cells = buffer px / 2 due to half-block). | | Color distinctness | New elements use colors distinct from immediate neighbours. | | cargo test | Connectivity test passes (cargo test --workspace, or just test). | | --debug-walkable | Rendered the overlay and visually checked no narrow / isolated walkable pockets near the new element. |
Skipping this checklist defeats the point of the skill — the whole reason it exists is that past sessions shipped invisible / unverified changes.
pack.toml keys; only add new ones if necessary..sprite file; verify row widths with the awk command above.embedded_pack.rs.[animations.foo] block to pack.toml.Point placement in SceneLayout::compute.build_walkable_mask (or a waypoint kind if it's interactive).DrawableKind::Foo variant + paint_drawable arm if z-sorting matters.cargo test -p pixtuoid-scene — the layout/walkable-connectivity and painter tests this checklist relies on live there since the scene split (-p pixtuoid-core no longer runs any of them).What we did: replaced the 20×8 pantry counter with a 32×10 design through 8 iterations:
cargo build --workspace was not rebuilding the snapshot example, so v6 was never actually rendered. Fixed by adding build.rs.Lessons: silhouette + color over detail, always rebuild the example explicitly, bump cols to 192 for the large variant.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | fail→pass | 10,285 | 4,252 | -59% | 1 | 1 | 0% | 1,584 | 3,835 | +142% | 0 | 0 | — |
case-20 | pass→fail | 15,975 | 2,935 | -82% | 1 | 1 | 0% | 2,566 | 3,288 | +28% | 0 | 0 | — |
case-01 | fail→fail | 3,416 | 7,329 | +115% | 1 | 1 | 0% | 259 | 3,540 | +1267% | 0 | 0 | — |
case-02 | fail→fail | 5,411 | 5,535 | +2% | 1 | 1 | 0% | 360 | 3,346 | +829% | 0 | 0 | — |
case-03 | fail→fail | 5,263 | 6,347 | +21% | 1 | 1 | 0% | 247 | 3,482 | +1310% | 0 | 0 | — |
case-04 | fail→pass | 10,139 | 3,788 | -63% | 1 | 1 | 0% | 1,628 | 3,683 | +126% | 0 | 0 | — |
case-05 | fail→pass | 20,615 | 3,821 | -81% | 1 | 1 | 0% | 1,143 | 3,776 | +230% | 0 | 0 | — |
case-06 | fail→fail | 15,845 | 4,582 | -71% | 1 | 1 | 0% | 2,339 | 3,878 | +66% | 0 | 0 | — |
case-07 | fail→pass | 8,492 | 2,436 | -71% | 1 | 1 | 0% | 1,289 | 3,473 | +169% | 0 | 0 | — |
case-08 | fail→pass | 13,090 | 5,172 | -60% | 1 | 1 | 0% | 1,969 | 3,845 | +95% | 0 | 0 | — |
case-09 | fail→pass | 9,757 | 2,222 | -77% | 1 | 1 | 0% | 1,383 | 3,354 | +143% | 0 | 0 | — |
case-14 | fail→pass | 8,572 | 3,755 | -56% | 1 | 1 | 0% | 1,379 | 3,730 | +170% | 0 | 0 | — |
case-10 | pass→pass | 15,074 | 10,891 | -28% | 1 | 1 | 0% | 2,166 | 4,628 | +114% | 0 | 0 | — |
case-11 | pass→pass | 13,276 | 7,686 | -42% | 1 | 1 | 0% | 1,982 | 4,188 | +111% | 0 | 0 | — |
case-12 | fail→pass | 9,820 | 2,754 | -72% | 1 | 1 | 0% | 1,555 | 3,410 | +119% | 0 | 0 | — |
case-13 | fail→pass | 10,350 | 2,404 | -77% | 1 | 1 | 0% | 1,585 | 3,447 | +117% | 0 | 0 | — |
case-15 | fail→pass | 9,495 | 2,225 | -77% | 1 | 1 | 0% | 1,411 | 3,387 | +140% | 0 | 0 | — |
case-16 | pass→pass | 14,988 | 6,354 | -58% | 1 | 1 | 0% | 2,239 | 3,914 | +75% | 0 | 0 | — |
case-17 | fail→pass | 16,458 | 3,547 | -78% | 1 | 1 | 0% | 2,270 | 3,547 | +56% | 0 | 0 | — |
case-18 | fail→pass | 7,558 | 2,517 | -67% | 1 | 1 | 0% | 1,067 | 3,496 | +228% | 0 | 0 | — |
case-21 | pass→fail | 13,879 | 4,569 | -67% | 1 | 1 | 0% | 2,466 | 3,230 | +31% | 0 | 0 | — |
case-22 | pass→fail | 10,120 | 4,684 | -54% | 1 | 1 | 0% | 1,856 | 3,238 | +74% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 17 counted toward the lift figure. The other 5 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +41 percentage points is the difference between those two pass rates over the 17 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.