From d26a4007beef6ebe62dd3b7ddf923995b2c00943 Mon Sep 17 00:00:00 2001 From: Pablo Stanley Date: Sat, 16 May 2026 08:35:38 -0700 Subject: [PATCH] Fix: PNG download baked full sprite sheet for animated faces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creator `download()` used the 5-arg form of `drawImage`, which stretched the whole image (including extra frame columns for blink/sequence parts) into the canvas — animated eyes showed open + closed side-by-side. Switched to the 9-arg form with a source rect of `(0, 0, NATIVE, NATIVE)` so only the first 32×32 frame of each layer is sampled. Mirrors what `drawOnCanvas` already does for live preview. Works for every layer (eyes/heads/body/top) and every kind (static/blink/sequence). Co-Authored-By: Claude Opus 4.7 (1M context) --- ROADMAP.md | 1 + app/src/app/creator.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 2f69018..16166f3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -166,6 +166,7 @@ When reviewing code, use the Before/After markdown table format from the skill. - [x] **Browse 429s.** `/browse` fires ~60 animated renders on first paint. Animated rate limit was 30/min per IP — 30 succeed, the rest break. Added `isSameOrigin(request)` that checks `Sec-Fetch-Site: same-origin` to exempt our own UI. External consumers still capped, but caps bumped to 120/min animated + 60/min OG. (PR #170) - [x] **Cache header revalidation.** `Cache-Control: immutable` was wrong once PR #156 made same-ID sprite art mutable. Swapped for 1-day fresh + 7-day stale-while-revalidate — art updates propagate within ~1 day without a manual dashboard purge. Manual purge still works for urgent rollout. (PR #166, #168, #169) - [x] **Animated `cheeky-terminal` (16-frame sequence) + `terminal-round` + `glasses` (blink).** Upgraded three previously-static eye parts to animated. `cheeky-terminal` is the first 16-frame sequence (fills one full super-loop). `ANIM_VERSION` 1→2 to bust CDN caches. Parts.ts docstring updated to include N=16 as valid sequence length. +- [x] **Client PNG download baked full sprite sheet.** Creator `download()` used 5-arg `drawImage(img, 0, 0, size, size)` which stretched whole sheet — animated faces showed both blink frames side-by-side. Switched to 9-arg form with source rect `(0, 0, NATIVE, NATIVE)` so only frame 0 is sampled. Works for every layer (eyes/heads/body/top), every kind (static/blink/sequence). ## Improve GIFs / Animated API diff --git a/app/src/app/creator.tsx b/app/src/app/creator.tsx index 2686205..5103973 100644 --- a/app/src/app/creator.tsx +++ b/app/src/app/creator.tsx @@ -370,7 +370,7 @@ export function Creator({ } for (const category of layerOrder) { const img = imagesRef.current[category]; - if (img) ctx.drawImage(img, 0, 0, size, size); + if (img) ctx.drawImage(img, 0, 0, NATIVE, NATIVE, 0, 0, size, size); } const link = document.createElement("a"); link.download = `pixabot-${size}x${size}.png`;