Files

25 KiB

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 (../<id>-32.png / -64.png), so frames and idle line up.

file size
<id>-walk-32.png, <id>-attack-32.png 128x160
<id>-walk-64.png, <id>-attack-64.png 256x320
<id>-<anim>-<size>-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):

S=art/generated/sprites/scripts/make_anim.py
python $S gen <id> <walk|attack> [front|back|both] [--suffix -2] [--strict] [--front-src F] [--print]
python $S pix <id> <walk|attack> [--front F] [--back B] [--view V=SPEC ...] [--add-colors "R,G,B;..."]

gen

  1. front request (<id>-<anim>-front-raw<S>.png, aspect_ratio 4:5): reference = the idle raw (../<id>-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 (<id>-<anim>-back-raw<S>.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):

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

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-<size>.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:

walk (biped):  "a looping walk cycle: the <name> walks forward in the direction it faces, at a steady pace, carrying <WEAPON> 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 <name> 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 <name> 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)