# Creature animations Generated on 2026-11-07 with `fal-ai/nano-banana-pro/edit` (`resolution: 2K`, `num_images: 1`, `output_format: png`), following the imp's digging animation (`../../imp-sprite/PROMPTS.md`). Every sheet has the layout of `imp-hack-*.png`: 4 frames across, 5 views down (front, front three-quarter turned right, side facing right, back three-quarter turned away to the right, back), transparent, the feet on the bottom row of every cell, the SAME body scale, feet row and palette as the creature's idle sheet (`../-32.png` / `-64.png`), so frames and idle line up. | file | size | | --- | --- | | `-walk-32.png`, `-attack-32.png` | 128x160 | | `-walk-64.png`, `-attack-64.png` | 256x320 | | `---preview.png` | frames scaled up (8x / 4x), the idle view in the right column, dotted feet row | `walk`: contact, passing, contact (other leg), passing; the creature walks in place (the game moves it). `attack`: wind-up, strike, impact, recover; the feet stay in place. ## Script `../scripts/make_anim.py` (Pillow venv: `/tmp/sprvenv/bin/python -I`; reads the key from `~/.config/dungeoncraft/fal-key`, never prints it): ```bash S=art/generated/sprites/scripts/make_anim.py python $S gen [front|back|both] [--suffix -2] [--strict] [--front-src F] [--print] python $S pix [--front F] [--back B] [--view V=SPEC ...] [--add-colors "R,G,B;..."] ``` ### gen 1. **front request** (`--front-raw.png`, `aspect_ratio 4:5`): reference = the idle raw (`../-raw.png`, the `--crop` of `../scripts/extra.txt` if any) at half size with the background (and any frame lines, e.g. the kobold's raw) repainted in flat key colour. Asks for the full 5x4 grid; rows 1-3 are used, rows 4-5 sometimes too (see below). 2. **back request** (`--back-raw.png`, `aspect_ratio 16:9`): references = the same idle reference + rows 1-3 of the front raw (`--front-src` picks another front raw). 2x4 grid. `--strict` adds a sentence that all eight figures, the strike frames included, are seen from behind (no visor/face/emblem) and that the back three-quarter row is turned clearly right. Prompts: built by `prompt_front` / `prompt_back` from the creature's idle description (`creatures.py`), the per-creature `WEAPON` and `ATTACK` texts and the generic `WALK_*` frame texts in `make_anim.py`. A batch for all creatures needs a `WEAPON` and an `ATTACK` entry for each one (the walk text is generic). `--print` shows the prompts. Example (kobold attack, front): ```text The attached image is a pixel art sprite sheet of the "Kobold" in five views (left to right: front, front three-quarter turned right, side facing right, back three-quarter turned away to the right, back). A small scaly lizard-folk warrior: ... It holds an upright wooden spear with a steel tip in its right hand. Create a new pixel art sprite sheet of exactly the same character - same proportions, colours, outline, pixel size, clothes and equipment - showing a spear thrust: the kobold holds the spear level with both hands and stabs it forward at an enemy in front of it. Layout: a grid of 5 rows and 4 columns, every figure the same size, evenly spaced, each in its own equal cell, all standing on the same baseline within their row. Rows (one view per row, top to bottom): 1 front view facing the viewer, 2 front three-quarter view turned to the right, 3 side view facing right, 4 back three-quarter view turned away to the right, 5 back view. Columns (animation frames, left to right): 1 wind-up - the spear pulled back, held level at waist height with both hands, the tip pointing forward, body leaning back; 2 strike - the spear pushed forward, body leaning forward; 3 impact - the spear fully thrust forward, arms stretched out, body lunging forward, a small white spark at the spear tip; 4 recover - the spear pulled back to the ready position, body upright. The feet stay in place in all four frames; only arms, weapon, head and upper body move. Every frame of a row keeps exactly that row's view direction (the side view row stays in profile facing right in all four frames, the back rows never show the face), and the character holds the same wooden spear with the steel tip in the right hand in every single frame. Style: identical to the attached sheet - crisp retro pixel art, hard square pixels, one-pixel near-black outline, limited palette, no anti-aliasing, no blur. Flat pure magenta (#FF00FF) background everywhere, no ground, no shadows, no text, no labels, no frame lines, no grid lines, no motion lines. ``` The first four front requests (`*-front-raw.png`) were sent without the "Every frame of a row keeps ..." sentence and without "no motion lines"; the knight attack strike frame also asked for "a short white motion arc behind the blade" (the model drew big white swoosh rings; removed). The back prompt is the imp's back prompt with the character's name and frame texts. ### pix Per view row: the row is cut out of the raw (bands found by the empty rows between them, falling back to cuts at the emptiest rows near even splits when weapons touch the next row), pixelized with `tools/pixelize_sprite.py --grid 1xN --size S --feet S-1 --outline-weight 0.6 --palette ../-S.png`, and: - **scale**: the step is iterated until the median *core height* of the row's figures equals the core height of the idle view. Core = from the first pixel row whose longest opaque run is at least 20% of the cell wide (a spear tip or a raised blade is narrower, the head is wider) to the last row with a run of at least 3 px. So a spear above the head or a sword raised in the wind-up does not shrink the body. - **position**: each frame is shifted so that its core bottom is on the feet row and the median centre of its core rows is at the idle view's centre (the tool itself centres by bounding box, which would push the body away from an outstretched weapon). Pixels pushed out of the cell are cut (logged as `CLIPPED n px`). - **check**: the log prints per view the ratio of the frames' median body width to the idle view's (`width ratio`, flagged beyond +-10%): the model sometimes redraws the character stockier or slimmer, which no scaling can hide. - `--view V=SPEC` takes the frames of view V from other rows or files: `FILE:BAND/NBANDS:COUNT:PICKS[;FILE:...]` (empty FILE = the front raw; PICKS like `0,2,1,3`, `m1` mirrors). Every source row is scaled on its own, so frames of different raws can be mixed. ## Requests (11 images, about $1.65 at ~$0.15 per 2K image) | raw | result | | --- | --- | | `knight-walk-front-raw.png` | rows 1-2 ok; row 3 had SIX figures (4 side + 2 back three-quarter), rows 4-5 mixed. Side row used (figures 0,2,1,3: stride, pass, stride, pass) | | `knight-walk-front-raw-2.png` (retry) | all 5 rows correct; front, front 3/4, back 3/4, back used; its side row shows the same stride four times | | `knight-attack-front-raw.png` | front and front 3/4 rows nearly identical, side row turned to the front in frames 2-3, big white swoosh rings: dropped | | `knight-attack-front-raw-2.png` (retry, new sentence) | rows 1-3 good (raised, over the helm, low at the left, ready) | | `knight-attack-back-raw.png` | strike frame (column 2) showed the visor and the chest emblem in both rows: dropped | | `knight-attack-back-raw-2.png` (retry, `--strict`) | good: all from behind, row 1 slightly turned | | `kobold-walk-front-raw.png` | spear missing in the passing frames of the front and side rows: dropped | | `kobold-walk-front-raw-2.png` (retry) | SIX frames per row, spear in nearly all; figures 0,1,3,4 used; back 3/4 row inconsistent | | `kobold-walk-back-raw.png` (`--strict`, reference = the first 4 columns of raw-2, `kobold-walk-front-raw-2-4cols.png`) | good back views but the spear vanishes in the passing frames; frames 0 and 2 used | | `kobold-attack-front-raw.png` | very good, rows 1-5 all right | | `kobold-attack-back-raw.png` | row 1 good; row 2 frames 2-3 are back three-quarter views instead of the back view | ## Commands used ```bash cd art/generated/sprites/anim; P="/tmp/sprvenv/bin/python -I"; S=../scripts/make_anim.py $P $S pix knight walk --front knight-walk-front-raw-2.png --view "2=knight-walk-front-raw.png:2/4:6:0,2,1,3" \ --view "3=knight-walk-front-raw-2.png:3/5:4:0,1,2,3" --view "4=knight-walk-front-raw-2.png:4/5:4:0,1,2,3" $P $S pix knight attack --front knight-attack-front-raw-2.png --back knight-attack-back-raw-2.png $P $S pix kobold walk --front kobold-walk-front-raw-2.png \ --view "0=:0/5:6:0,1,3,4" --view "1=:1/5:6:0,1,3,4" --view "2=:2/5:6:0,1,3,4" \ --view "3=kobold-walk-back-raw.png:0/2:4:0;:3/5:6:4;kobold-walk-back-raw.png:0/2:4:2;:3/5:6:4" \ --view "4=:4/5:6:0,1,3,4" $P $S pix kobold attack --view "4=kobold-attack-back-raw.png:1/2:4:0;kobold-attack-front-raw.png:4/5:4:1,2;kobold-attack-back-raw.png:1/2:4:3" ``` No extra colours were needed: the steel of both weapons is in the idle palettes. ## Quality - **knight-walk**: usable. Height and feet line up with idle (core height = the full cell, as in idle), no vertical shift. The model drew the knight about 5-15% wider/stockier than the idle sheet (width ratio 1.01-1.18 in the log), so switching idle -> walk shows a slightly broader knight. Leg motion is small in the front views; the side view comes from the first raw (a separate image, 6 figures in that row) and is a real stride/pass cycle but differs a little in shading from the other rows. - **knight-attack**: good motion, readable in every view (sword raised over the shoulder, over the helm, low at the left, ready). Same stockiness as the walk (width ratio up to 1.3 in the front three-quarter row, partly the sword). The knight fills the whole cell height, so a sword raised above the helm is cut at the top of the cell (the tool crops it; not counted in the CLIPPED log). The low impact blade loses up to 5 px at the cell side (64). - **kobold-walk**: usable. Body, head and feet line up with idle in every view (core heights equal, no vertical shift). The leg motion is subtle (the model draws small steps), the spear stays in the hand in all frames. Back three-quarter: frames 2 and 4 are the same figure (from the front raw-2), frames 1 and 3 come from the back request and are drawn a little narrower, so that view jitters slightly. - **kobold-attack**: good. Clear thrust in all views; body and feet line up with idle. The level spear is longer than the cell, so its butt or tip is cut at the cell edge in most frames (up to 8 px at 32, 33 px at 64 in the front impact frame). The front three-quarter wind-up holds the spear pointing backwards. Back view: frames 1 and 4 from the back request, frames 2-3 from row 5 of the front raw (the back request had back three-quarter figures there). The white impact spark is mapped to the nearest palette colour (light grey) or lost. ## Raw files The raws `*-raw*.png` are large and git-ignored (`art/generated/sprites/anim/*-raw*.png`). # Batch: walk for all, die for all, attack for six (2026-11-07/08) After the pilot: `walk` for the other 29 creatures (incl. the imp), `die` for all 31, `attack` for ogre, spider, mage, archer, dwarf and paladin. `manifest.json` lists the accepted animations per creature (`{"id", "kind", "anims"}`), `progress.json` the image count and the status and notes per sheet. `sheet-notes.txt` is the review list (input of `anim_manifest.py`), `pix-commands.sh` holds every pix command that was run (the last one per sheet is the accepted one). Requests: **79 images, about $11.85** (+ the pilot's 11). Three requests failed with "connection reset"; the image counter counts only delivered images, so if fal billed those, add up to $0.45. No sheet was skipped. ## Script changes (`../scripts/make_anim.py`) - `die` as a third animation; `gen`/`pix` accept `imp` (raw = the lower row of `../../imp-sprite/raw-1.png`, crop `0,0.27,1,0.72`; idle = `../../imp-sprite/imp-.png`; description in `BY_ID["imp"]`, kind `Worker`). - Wording per creature type (`kind_of`: `HOVER`, `SIEGE` from `creatures.py`, `QUAD` = spider, cave bear, beetle, else biped): walk = `WALK_FRONT` (biped), `QUAD_FRAMES[id]` (own gait), `HOVER_FRAMES` (hovering wing flap; the first, softer wording was used only for the bat), `SIEGE_FRAMES` (rolling, wheels turning); die = `DIE[type]` (see below). - `WEAPON` entries for every creature with an item (so the walk keeps it), `ATTACK` for the six. - **die pixelization**: the step is set by frame 1 only (still standing, so its core height = idle); frames 2-4 keep their position relative to frame 1 as drawn in the raw (the tool centres every figure in its cell; the source offsets from the column centres are put back), the lowest part on the feet row, pulled back into the cell when it fits. Width check on frame 1 only. A 5th field in a `--view` segment (`FILE:BAND/N:COUNT:PICKS:REF`) names the figure of that row that sets the scale (for mixing a falling figure from another row). - `strip_lines`: dark horizontal lines across most of the image (ground lines, row separators the model drew; at most 12 px thick, >75% of the width) are keyed out; a flat background in another colour (the catapult raws came white and pale blue) is keyed out too. - `row_bands`: tiny bands (a halo or sparkles floating above a row) belong to the next row (before, a priest's halo landed in the row above and broke the scaling). - `fal`: retries on 429/5xx/connection errors (60 s, 120 s, ...), counts every delivered image in `progress.json` (file lock, so parallel runs are safe). - `../scripts/peek.py` (raws side by side, small), `../scripts/pv.py` (32 previews side by side, half size) for reviewing; `../scripts/anim_manifest.py NOTES` writes `manifest.json` and the sheet notes into `progress.json`. ## Prompts Same templates as the pilot (`prompt_front` / `prompt_back`, `--print` shows them). The animation texts: ```text walk (biped): "a looping walk cycle: the walks forward in the direction it faces, at a steady pace, carrying as in the idle pose" walk (quad): same, columns from QUAD_FRAMES (spider: legs 1 and 3 on the left and 2 and 4 on the right stepping forward ...; beetle: front and back legs on the left and the middle leg on the right ...; cave bear: right front paw and left hind paw ...) walk (hover): "a looping hovering flight cycle: the hovers in place in the air, flapping its wings" columns: 1 upstroke - the wings raised straight up high above the head, the wing tips pointing up, the body at its highest point; 2 downstroke - the wings swept out level to the sides, fully spread; 3 down - the wings pulled down below the body, the wing tips pointing down, the body at its lowest point; 4 recovery - the wings half raised and folded a little, swinging back up + "The creature stays in the middle of its cell ...; the wings flap with big, clearly different wing positions in the four frames ..." walk (siege): "rolling forward: the rolls forward in the direction it faces, its wooden wheels turning" (columns: wheel marks at 0, 1/4, 1/2, 3/4 turn) die (biped): "a death: the character is struck by a fatal blow and falls over backwards (away from the direction it faces) onto its back, where it lies still - in every view it falls the same way, backwards relative to its own facing" columns: 1 hit - recoiling from the blow, still standing upright on both feet, head thrown back, eyes shut in pain, arms flung out; 2 falling - tipping over backwards, the knees buckling, the arms flailing; 3 hitting the ground - landing on its back, the body flat and low, limbs bouncing up a little; 4 lying still - lying flat on its back on the ground, limbs limp, eyes closed or crossed out, motionless + "Frame 1 has the same size and position as the idle figure; in frames 2-4 the body sinks to the ground, the lowest part always on the baseline of the row, and stays within its own cell. Frame 4 lies flat and low on the ground." die (quad): staggers, collapses and rolls over onto its side, legs curled up die (hover): wings fail, drops out of the air, lies with the wings spread flat die (siege): "its destruction: the machine is smashed - planks crack and splinter, a wheel breaks off, and it collapses into a heap of broken wood" attack: ATTACK[id] (ogre: two-fisted smash; spider: rearing pounce and bite; mage: staff thrust casting a purple shadow orb; archer: draw from the quiver, aim, release, lower; dwarf: overhead pickaxe blow; paladin: two-handed diagonal swing with the glowing blade) ``` ## Procedure 1. All front requests in parallel (10 at a time; this hit fal's rate limit, hence the retries). 2. Each front raw was looked at; a back request was only made when rows 4-5 of the front raw were unusable (the model now often draws all five rows correctly). Back requests: bat, ram walk and ogre die (before this rule), ogre/gargoyle/wyrm/giant walk, mage/ogre/archer/dwarf attack (`--strict`), dwarf attack again. 3. `pix` with per-view picks (`pix-commands.sh`), then every 32 preview was checked against idle. ## Results and quality General: - **die**: in every raw the model draws frame 1 in the row's view, but frames 2-4 as the *same* sideways fall in all five rows (it ignores the view once the body falls; a back request for the ogre showed the same). So the dying body falls the same way on screen whatever the view, usually head to the right. It still reads well as a death; asking for more would need one request per view. The lying body is often wider than the cell and cut at the cell sides (revenant, imp, the knight's sword). - Several creatures come out wider/bulkier than their slim idle sheet (paladin, archer, thief, monk, fairy and wyrm wings), so switching idle -> animation shows a slightly broader figure. - Siege engines: the wheel turning is not visible at 32 px; the walk is mostly a gentle rock. Per sheet (also in `progress.json`): | sheet | status | notes | | --- | --- | --- | | archer-attack | accepted | front rows from the front raw, back rows from the back raw (--strict); the front three-quarter archer is drawn larger/wider than idle; side view: the draw is small | | archer-die | accepted | good | | archer-walk | accepted | raw had 6 frames per row (0,1,3,4 used); drawn a bit wider than idle, bow thinner | | bat-die | accepted | good drop and splayed wings | | bat-walk | accepted | all 5 rows from the front raw; wing flap is subtle (back raw had a smeared middle row, unused) | | battlemage-die | accepted | good (hat falls off in the back view) | | battlemage-walk | accepted | good | | beetle-die | accepted | good: tips over onto its back, legs up | | beetle-walk | accepted | legs barely visible (small steps); two stray antenna pixels above the front three-quarter frames 1 and 3 | | catapult-die | accepted | the model drew a pale blue background (keyed out); in the back view frames 2-3 the wreck is drawn larger than the machine | | catapult-walk | accepted | the model drew a white background (keyed out); wheel turning not visible, the machine only rocks slightly; back three-quarter drawn smaller than idle | | cavebear-die | accepted | good; front view frame 1 a little smaller than the idle bear | | cavebear-walk | accepted | good four-legged gait; side view touches the cell edges | | chainbreaker-die | accepted | good | | chainbreaker-walk | accepted | good | | cogling-die | accepted | good | | cogling-walk | accepted | good | | dwarf-attack | accepted | back raw needed a retry (first one showed the face in the impact frames); back three-quarter impact frame from the back row | | dwarf-die | accepted | good | | dwarf-walk | accepted | good | | fairy-die | accepted | good | | fairy-walk | accepted | wings drawn bigger than in idle; back 3/4 = 2 frames alternating (others showed the face) | | gargoyle-die | accepted | good | | gargoyle-walk | accepted | back three-quarter frames 0,1,2,1 of the front raw (frame 4 showed the face), back view from the back raw (both back rows came out straight from behind) | | giant-die | accepted | good (the club drops) | | giant-walk | accepted | front row frames 0,2,3,2 (the club vanished in one frame); back view from the back raw (2 frames alternating, the others lost the club) | | imp-die | accepted | frames 2-4 are the same sideways fall in every view (model limitation, as in all die sheets); the lying imp touches the cell edge | | imp-walk | accepted | front and front 3/4 rows: frames 0,1,2,1 (frame 4 of the raw was a back view) | | inquisitor-die | accepted | good: the holy book flies off; frame 1 wider than idle (arms flung out) | | inquisitor-walk | accepted | back three-quarter frames 0,1,2,1 (frame 4 was a straight back view) | | jailer-die | accepted | the raw had 5 figures in rows 2-5 (standing figures repeated, no falling frame there): the falling frame of rows 3-5 is borrowed from the front row; thin remnants of ground lines the model drew show under the lying body (mostly at 64) | | jailer-walk | accepted | drawn a little wider than idle; the dark grey skin against the near-black outline looks noisy (as in idle) | | knight-die | accepted | side and back rows: the raw had 5 figures (3 standing + fall + lying): frames 0,3,4,4 used, so the hit-the-ground and lying frames are the same there | | kobold-die | accepted | first front raw dropped (mid-air flip with a shadow, back rows not falling); retry: back views take frame 1 from their own row and frames 2-4 from the front row | | mage-attack | accepted | good: staff raised, thrust, a dark orb leaves the gem; back rows from the back raw | | mage-die | accepted | good (eyes closed, so the glowing eyes are gone) | | mage-walk | accepted | good | | monk-die | accepted | good | | monk-walk | accepted | wider than idle, fists lowered while walking (idle raises them) | | ogre-attack | accepted | front rows from the front raw, back rows from the back raw (--strict); back view frame 2 taken from the back three-quarter row (the back row showed the face there) | | ogre-die | accepted | frames 2-4 are the same sideways fall in every view (the model ignores the view once the body falls); back views from the back raw | | ogre-walk | accepted | back views from the back raw (the front raw had no back three-quarter row) | | owl-die | accepted | good | | owl-walk | accepted | good flapping | | paladin-attack | accepted | all 5 rows from the front raw; clear two-handed swing; blade cut at the cell edge in the impact frames; bulkier than the slim idle | | paladin-die | accepted | drawn bulkier than the slim idle paladin; the white/gold armour gets blotchy when lying; sword drops beside the body | | paladin-walk | accepted | drawn wider/stockier than idle; back 3/4 = 2 frames alternating (the row mixed front and back figures) | | priest-die | accepted | the halo is lost in frame 1, so frame 1 is scaled a little larger than idle (about one tenth); the halo comes back while falling | | priest-walk | accepted | first pixelization lost the halo of the front row (a floating halo was cut into the row above); fixed in row_bands and re-run | | priestess-die | accepted | good | | priestess-walk | accepted | good | | ram-die | accepted | breaks into a heap of planks; frames 2-4 are the same sideways collapse in every view | | ram-walk | accepted | front view static (frame 1 x4: the other front frames were turned); back view from the back raw; wheel turning barely visible, slight rocking | | ranger-die | accepted | the bow stands apart from the ranger in frame 1 of the side and back rows and is dropped there; grid lines in the raw removed by the tool | | ranger-walk | accepted | good | | revenant-die | accepted | the lying skeleton is longer than the cell and is cut at the cell sides in frames 3-4 | | revenant-walk | accepted | 6 frames per row with mixed views, picks per row; small jitter | | spider-attack | accepted | raw had 6 rows; only the wind-up (front legs raised) reads clearly, the bite frames move little | | spider-die | accepted | back three-quarter = back view (as in idle); collapses with curled legs | | spider-walk | accepted | raw had 6 rows (an extra front three-quarter row turned left); back three-quarter = back view as in idle | | thief-die | accepted | good | | thief-walk | accepted | raw had 5 frames per row (0,1,3,4 used); a little wider than idle | | trader-die | accepted | good | | trader-walk | accepted | good | | wyrm-die | accepted | frame 1 shows the wyrm with wings spread, wider than the idle pose; lying frames are a sideways dragon in every view | | wyrm-walk | accepted | strong wing flap, but the wings are spread much wider than in the idle pose (idle has them folded); back three-quarter = 2 frames alternating from the back raw (the other two showed the face) |