beautify-decoration

star 280

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.

IvanWng97 By IvanWng97 schedule Updated 6/7/2026

name: beautify-decoration version: 1.0.0 description: "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." metadata: scope: "pixtuoid repo only"

beautify-decoration (v1)

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.

When to use

  • Redesigning an existing decoration sprite (pantry, lounge, meeting, cubicle decor)
  • Adding a new fixture (pendant lamp, water cooler, chalkboard, etc.)
  • User says "items look too small / don't read like X / blend together"
  • After making sprite edits and "I don't see any change"

The visual-iteration loop

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|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).

Sharp edges (the things that wasted time during the pantry session)

1. The rebuild trap

  • cargo build --release --workspace does not rebuild examples. Use cargo build --release --example snapshot when iterating on examples/snapshot.
  • include_str! in crates/pixtuoid/src/tui/embedded_pack.rs bakes sprite files at compile time. A build.rs exists at crates/pixtuoid/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.
  • If unsure, verify with: strings target/release/examples/snapshot | grep "<some unique string from your sprite>".

2. Snapshot defaults hide the large sprite variants

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-core/src/layout.rs:compute().

3. Visual-inspection helper

The full PNG is too big to grok at a glance and too small at thumbnail. Crop the relevant quadrant with PIL:

from 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 Read the cropped PNG — you (Claude) can see PNG content via the Read tool.

PIL is available system-wide (installed via pip3 install --user --break-system-packages Pillow). If a fresh environment misses it, install once.

4. Resolution budget

  • Each sprite pixel ≈ half a terminal cell (half-block compression).
  • Subzones smaller than ~5 display cells wide blur into pixel noise — users can't read them.
  • Sub-pixel detail (a 1-cell handle, a 1-cell stripe) is invisible. Iterate on silhouette + color identity, not pixel polish.
  • A 32×10 sprite has only ~16 display cells of width. Three zones of ~5 cells each is the practical max for legibility. Drop items; don't shrink them.

5. Identity mistakes that look identical to each other

Symptoms of weak identity:

  • Transparent body (.): the wall color shows through, weakening the silhouette. Use a solid fill color for appliances.
  • All-dark appliances: a row of 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).
  • Symmetric H-frame on a white box → reads as washing machine, not fridge. Use asymmetric handles (single-side handle, or center-French-door pair).
  • Cyan + blue dispenser dots next to each other → reads as cyan-cyan because b is dark and gets dim. Space them out or use c + r.

6. Sprite-format pitfalls

  • Every row in a .sprite file must have exactly the same number of space-separated cells. Off-by-one is the most common bug.
  • Verify with: awk '/^@/{next}/^#/{next}NF{print NR": "NF}' crates/pixtuoid/sprites/default/foo.sprite — all NF values must match.
  • Or visualize packed rows: awk '/^@/{next}/^#/{next}NF{for(i=1;i<=NF;i++)printf "%s",$i;print " ["NF"]"}' foo.sprite.
  • Palette keys must be unique RGB (the per-agent recolor pass substitutes by RGB equality — see embedded_pack.rs header comment).
  • Reuse existing palette keys when possible; new keys go in crates/pixtuoid/sprites/default/pack.toml [palette] section.

7. Layout integration checklist

When a sprite changes size:

  1. Update the walkable-mask footprint in core::layout::build_walkable_mask — there's a per-WaypointKind match arm with hardcoded (w, h) tuples.
  2. If the obstacle is a non-waypoint (plant, wall decor, pod decor), update the corresponding mark_blocked call too.
  3. Run cargo test -p pixtuoid-core — the walkable_mask_is_fully_connected_across_buffer_sizes test catches mask/sprite mismatches by trying multiple buffer sizes and asserting BFS reach from the door.
  4. If the connectivity test fails on the smallest buffer (96×70), the sprite is too big for that pantry. Add a _small variant + conditional pick (see pantry_counter_size in SceneLayout for the pattern).
  5. Update animation list in crates/pixtuoid/sprites/default/pack.toml and embedded_pack.rs to include both foo.sprite and foo_small.sprite if you added a variant.

8. Live binary uses different binary than snapshot

./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.

Self-critique checklist — MANDATORY before every SendUserFile

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 --features pixtuoid-core/test-renderer).
--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.

Workflow when adding a NEW decoration

  1. Sketch the design as a list of cells per row (count exactly).
  2. Pick a palette: reuse pack.toml keys; only add new ones if necessary.
  3. Write the .sprite file; verify row widths with the awk command above.
  4. Add the include_str! line to embedded_pack.rs.
  5. Add the [animations.foo] block to pack.toml.
  6. Decide where it lives in the layout — add a Point placement in SceneLayout::compute.
  7. Add the obstacle footprint to build_walkable_mask (or a waypoint kind if it's interactive).
  8. Add a DrawableKind::Foo variant + paint_drawable arm if z-sorting matters.
  9. Run cargo test -p pixtuoid-core.
  10. Snapshot + iterate.

Recap of the pantry session (case study)

What we did: replaced the 20×8 pantry counter with a 32×10 design through 8 iterations:

  • v1–v3: Too crowded, 6 zones × 3 cells each = unreadable.
  • v4: Simplified to 3 zones (fridge / coffee / microwave-snacks) at 8/10/10 cells.
  • v5–v6: Tried adding detail (handle pairs, dividers). User said "no difference between v5/v6" — too subtle to read at scale.
  • v7: Discovered cargo build --workspace was not rebuilding the snapshot example, so v6 was never actually rendered. Fixed by adding build.rs.
  • v8: Color-coded for identity — solid WHITE fridge vs. dark coffee + dark microwave. Strong silhouette differentiation. (Honest self-critique: still looks washing-machine-y due to H-frame.)

Lessons: silhouette + color over detail, always rebuild the example explicitly, bump cols to 192 for the large variant.

Install via CLI
npx skills add https://github.com/IvanWng97/pixtuoid --skill beautify-decoration
Repository Details
star Stars 280
call_split Forks 15
navigation Branch main
article Path SKILL.md
More from Creator