Compare commits

...
41 Commits
Author SHA1 Message Date
Richard ZinkandClaude Opus 5.5 65ed76865d Generated gear parts (helmet, shoulder pieces, crown, cape, horns, plume) instead of drawn shapes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:11:47 +01:00
Richard ZinkandClaude Opus 5.5 af330c09b7 The dead leave remains for good: a pool of blood and bones, armour bits from heroes, splinters from machines; dust where they fall
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:06:36 +01:00
Richard ZinkandClaude Opus 5.5 8d2362fc11 Bestiary portraits in the high-res style: a painted pixel-art bust for every creature
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:05:41 +01:00
Richard ZinkandClaude Opus 5.5 a24fc51605 Holiday headwear on every unit: Christmas hats, carved pumpkins, bunny ears
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:04:26 +01:00
Richard ZinkandClaude Opus 5.5 6ff1cc5f8d High-res textures built on first use: no cost in the pixel-art style, the 128x128 atlas only when zoomed in
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:01:43 +01:00
Richard ZinkandClaude Opus 5.5 fb71009110 Walk and attack animations for knight and kobold; the dead topple, sink and fade (death animations where drawn)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:51:36 +01:00
Richard ZinkandClaude Opus 5.5 16e299d1f1 Mossgrim's heart: a gnarled moss-grown tree rising from the throne, the heart glowing in its hollow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:49:00 +01:00
Richard ZinkandClaude Opus 5.5 45060e85b7 The other masters' hearts: Mossgrim a mossy knot of branches, Lady Nightwell a blue lily, Saffrax a golden sun in brass rings, each with its own beat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:44:43 +01:00
Richard ZinkandClaude Opus 5.5 d87f862199 Buildings textured by material (stone, iron, wood, cloth); the heart in crystal facets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:43:24 +01:00
Richard ZinkandClaude Opus 5.5 d951c16902 High-res interface icons: tools, towers, traps, doors, spells, orders and siege
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:41:08 +01:00
Richard ZinkandClaude Opus 5.5 37e5250c52 Bestiary room pictures from the high-res atlas in the high-res style
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:36:17 +01:00
Richard ZinkandClaude Opus 5.5 0ada04f4fb High-res: bestiary shows the detailed sprites, toolbar buttons on stone
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:33:41 +01:00
Richard ZinkandClaude Opus 5.5 ffad92b641 Detailed sprites: frames for walk and attack animations, gear laid over them, hit flash in the shader, 32x32 level in its own texture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:32:29 +01:00
Richard ZinkandClaude Opus 5.5 01e5326721 Detailed imp-style sprites for all 30 creatures in the high-res style; sprite texture in bands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:21:58 +01:00
Richard ZinkandClaude Opus 5.5 210f17773a Debug demo map: four allied masters with every room and creature against an endless hero gate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:04:34 +01:00
Richard ZinkandClaude Opus 5.5 5f70992cfb Room and tool buttons draw from the high-res atlas in the high-res style
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:50:47 +01:00
Richard ZinkandClaude Opus 5.5 8c33051913 Desktop builds with their own profile: thin LTO and 16 codegen units, compiled on all cores
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:49:28 +01:00
Richard ZinkandClaude Opus 5.5 4175ca7f97 Holiday switch in the debug menu; darker wood for the treasury chests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:44:56 +01:00
Richard ZinkandClaude Opus 5.5 8f670554c7 Holidays: Christmas and Easter beside Halloween, two weeks before to a few days after; decorations in the dungeon and around the title
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:40:27 +01:00
Richard ZinkandClaude Opus 5.5 c23c4d28ec Furniture materials: wood, stone, iron, cloth, straw, fur, book spines and spellbook pages in both styles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:37:31 +01:00
Richard ZinkandClaude Opus 5.5 9cba545bc7 High-res textures take on the pixel-art colours; treasury and mushroom garden closer to the pixel art
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:34:20 +01:00
Richard ZinkandClaude Opus 5.5 2e905fe374 High-res textures for all rooms, special floors, walls, bridges, outdoors and objects; reinforced wall as castle masonry in both styles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:25:50 +01:00
Richard ZinkandClaude Opus 5.5 da40b0875e Reinforced wall as roughly hewn stone in both styles (high-res with five variants)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:15:36 +01:00
Richard ZinkandClaude Opus 5.5 ca8494aea0 Claimed floors back to fitted flagstones in the masters' colours with damage; high-res floor decals
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:11:41 +01:00
Richard ZinkandClaude Opus 5.5 362f4e7670 Five freely combinable variants for floor, earth, rock and ore textures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:02:55 +01:00
Richard ZinkandClaude Opus 5.5 ad668ec123 Claimed floors: grey castle flagstones with an inlay in the owner's colour and engraving
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:01:02 +01:00
Richard ZinkandClaude Opus 5.5 72c44ae8d0 Claimed floors reworked: roughly hewn castle flagstones worn smooth, no visible tile grid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:52:33 +01:00
Richard ZinkandClaude Opus 5.5 aab466cfbe High-res claimed floors for all four masters: five slabs each with own engravings and damage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:41:07 +01:00
Richard ZinkandClaude Opus 5.5 50f8af15c7 High-res ore textures: gold, silver, ruby and diamond as lumps in the earth, no veins
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:32:00 +01:00
Richard ZinkandClaude Opus 5.5 9df16607ed High-res tiles also in 128x128 for close views
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:29:58 +01:00
Richard ZinkandClaude Opus 5.5 8197b07ac5 High-res tile textures: floor, claimed floor, earth, rock, gold, churning water and lava
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:26:22 +01:00
Richard ZinkandClaude Opus 5.5 69a6b13006 Imp in 64x64 when close, 32x32 at normal zoom; detailed sprites in their own texture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:17:17 +01:00
Richard ZinkandClaude Opus 5.5 2d1ed177a7 Imp digging animation and gold sack in high-res; sack also on the small imp instead of the coin sign
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:08:07 +01:00
Richard ZinkandClaude Opus 5.5 76f3b3c7b2 Pixel art style coarser: 270 world rows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:53:28 +01:00
Richard ZinkandClaude Opus 5.5 807a124a0b Graphics style: pixel art (360 rows) or high-res (1080 rows, detailed sprites)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:50:26 +01:00
Richard ZinkandClaude Opus 5.5 928c34e78b Detailed 32 px imp sprite and finer world pixel rows (540, 1080)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:41:29 +01:00
Richard ZinkandClaude Opus 5.5 10af85fd95 Bump version to v202611-00077
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:35:19 +01:00
Richard ZinkandClaude Opus 5.5 4b8afa1fb2 Imp sprite test: generated sheet, pixelize tool and 24/32 px results
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:35:09 +01:00
Richard ZinkandClaude Opus 5.5 b973483bae Add generated soul core heart model
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:31:08 +01:00
Richard ZinkandClaude Opus 5.5 bfd6662b21 Generated soul core heart: concept image, preview and prompts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:29:38 +01:00
Richard ZinkandClaude Opus 5.5 361e1e4108 Interface size in finer percent steps; fixed sizes no longer double the interface
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:22:56 +01:00
796 changed files with 6528 additions and 287 deletions

No files matched your search

+6
View File
@@ -5,3 +5,9 @@ www/dungeoncraft.wasm
www/mq_js_bundle.js
play/
dist/
art/generated/creature-previews/
art/generated/sprites/*-raw*.png
art/generated/sprites/anim/*-raw*.png
art/generated/sprites/scripts/__pycache__/
art/generated/portraits/*-raw*.png
art/generated/portraits/scripts/__pycache__/
+8
View File
@@ -18,6 +18,14 @@ codegen-units = 1
[profile.dev]
opt-level = 1
# desktop builds (tools/build_desktop.py): optimised like release, but compiled on all cores
# (several codegen units, thin LTO); release keeps one unit and full LTO for the smallest,
# fastest WASM
[profile.desktop]
inherits = "release"
lto = "thin"
codegen-units = 16
# desktop builds: system file dialog (xdg portal on Linux) and sound output
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rfd = { version = "0.17", default-features = false, features = ["xdg-portal"] }
+1 -1
View File
@@ -1 +1 @@
v202611-00076
v202611-00077
+12
View File
@@ -0,0 +1,12 @@
# Gear parts (high-res)
The parts laid over the detailed sprites as their gear (helmet, shoulder piece, crown, cape,
horn, plume; `GEAR_*` and `gen_gear` in `src/gfx.rs`). Generated on 2026-11-08 with
`fal-ai/nano-banana-pro` (`aspect_ratio: 3:2`, `resolution: 2K`, one image), the prompt in
`batch.py`: six pieces in light neutral grey on magenta, so the game tints them (bronze, steel,
gold; the cape red for creatures, blue for heroes).
Processing (inline python, see the session): magenta keyed out, every piece cropped to its
picture, the horn mirrored, turned into light grey (luminance x 1.25), scaled to its place in the
gear texture (the old rects times four: helmet 96x48, shoulder 48x36, crown 80x40, cape 112x160,
horn 28x40, plume 28x48) with hard alpha; copied to `assets/textures/gear/<part>.png`.
+12
View File
@@ -0,0 +1,12 @@
# The gear parts laid over the detailed sprites (src/gfx.rs GEAR_*), 2026-11-08: one sheet,
# light neutral grey so the game tints them (bronze, steel, gold; the cape red or blue).
PROMPT = ("A sheet of six separate equipment pieces for a dark-fantasy pixel-art game, arranged in a grid of 3 columns and 2 rows, each piece centred "
"in its own cell and filling most of it, all in the same style: hand-painted pixel art, crisp edges, a dark near-black outline, shading lit "
"from the upper left, and every piece in LIGHT NEUTRAL GREY tones only (no other colours), so it can be tinted later. The background is a "
"completely flat pure magenta (#FF00FF) everywhere - no frames, no grid lines, no text. The pieces, in reading order: "
"1. a round metal helmet seen from the front, a dome with a riveted rim, no face, no visor; "
"2. a single rounded shoulder plate (pauldron) seen from the front, with two rivets; "
"3. a crown with three pointed spikes and small round gems; "
"4. a long cape seen from behind, hanging down, wider at the hem, with folds; "
"5. a single curved horn pointing up, slightly bent to the right; "
"6. a feather plume, a tall bushy crest.")
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+66
View File
@@ -0,0 +1,66 @@
# Interface icons (high-res)
Generated on 2026-11-07 with `fal-ai/nano-banana-pro` (`resolution: 2K`, one image per sheet,
`aspect_ratio: 21:9` for the 5-column sheets, `16:9` for the spells). `batch.py` holds the
prompts: the shared text below followed by the numbered icon descriptions.
```text
A sheet of game icons for a dark-fantasy dungeon strategy game, arranged in a grid of {cols} columns and {rows} rows, every icon centred in its own equal square cell and filling about 80 percent of it, all icons the same size and in the same style: hand-painted pixel art, chunky clear shapes, crisp edges, a dark near-black outline, rich but slightly muted colours, readable at small size, light from the upper left. The background is a completely flat pure magenta (#FF00FF) everywhere, also between the icons - no frames, no grid lines, no cell borders, no text, no labels, no numbers. The icons, in reading order (left to right, top to bottom):
```
## tools (5x2)
- `I_PICK`: a pickaxe for digging
- `I_HAND`: a clawed green monster hand reaching to grab
- `I_SELL`: a small pile of gold coins with a curved arrow (selling)
- `I_FLAG`: a red rally flag on a pole
- `I_BOLT`: a yellow lightning bolt
- `I_HEAL`: a glowing green healing cross with sparkles
- `I_SUMMON`: a glowing violet summoning circle with a small imp silhouette
- `I_BRIDGE`: a small wooden plank bridge arch
- `I_WALL`: a block of grey stone wall with an upward arrow
- `I_STEEL`: a riveted dark steel wall plate
## towers (5x2)
- `I_ARROW`: a small wooden watchtower with an arrow
- `I_CANNON`: a squat stone tower with a black cannon
- `I_MAGIC`: a slender dark tower with a glowing purple orb on top
- `I_RUNE`: a dark stone obelisk with a glowing red rune and bronze bands
- `I_DRAWBRIDGE`: a raised wooden drawbridge with chains over water
- `I_RAM`: a battering ram: a tree trunk with an iron cap under a small wooden roof on wheels
- `I_CATAPULT`: a wooden catapult with a stone in its arm
- `I_ORDERS`: two crossed swords (attack order)
- `I_ORDERS+1`: a round shield (guard order)
- `I_FREEZE`: a glowing pale-blue snowflake (frost spell)
## traps (5x2)
- `I_TRAPS`: a floor trap with sharp metal spikes
- `I_TRAPS+1`: a floor vent puffing a green poison cloud
- `I_TRAPS+2`: a floor nozzle shooting a jet of fire
- `I_TRAPS+3`: a big round rolling boulder
- `I_TRAPS+4`: a brass alarm bell
- `I_TRAPS+5`: a glowing red rune mine on the floor
- `I_TRAPS+6`: a floor trap with a glowing blue frost crystal
- `I_TRAPS+7`: a wooden door with iron hinges
- `I_TRAPS+8`: a heavy iron door with rivets
- `I_TRAPS+9`: a magic door glowing with purple runes
## spells (4x2)
- `I_SPELLS`: a glowing all-seeing eye (seer's eye spell)
- `I_SPELLS+1`: falling rocks from a cracked ceiling (cave-in spell)
- `I_SPELLS+2`: a winged boot (haste spell)
- `I_SPELLS+3`: a fist of grey stone (stoneskin spell)
- `I_SPELLS+4`: gold coins raining down (gold rain spell)
- `I_SPELLS+5`: a fading translucent ghostly figure (invisibility spell)
- `I_SPELLS+6`: a green skull in a sickly cloud (plague spell)
- `I_SPELLS+7`: cracked shaking ground with rocks jumping (quake spell)
## Processing
`tools/cut_icons.py <sheet>-raw.png <sheet> --grid 2x5` (spells `--grid 2x4`) keys out only nearly
pure magenta (violet glows stay), crops every icon, centres it in a square and scales it to 64x64
and 128x128 with hard alpha. The strips go to `assets/textures/icons/`; the game puts them into
the high-res atlases' icon cells (`HD_ICONS` in `src/gfx.rs`).
+29
View File
@@ -0,0 +1,29 @@
# The interface icons of the high-res style (2026-11-07): four sheets, each a grid of icons.
# (sheet name, columns, rows, [(atlas cell constant, description)])
STYLE = ("A sheet of game icons for a dark-fantasy dungeon strategy game, arranged in a grid of {cols} columns and {rows} rows, every icon "
"centred in its own equal square cell and filling about 80 percent of it, all icons the same size and in the same style: "
"hand-painted pixel art, chunky clear shapes, crisp edges, a dark near-black outline, rich but slightly muted colours, readable at small size, "
"light from the upper left. The background is a completely flat pure magenta (#FF00FF) everywhere, also between the icons - "
"no frames, no grid lines, no cell borders, no text, no labels, no numbers. The icons, in reading order (left to right, top to bottom): ")
SHEETS = [
("tools", 5, 2, [
("I_PICK", "a pickaxe for digging"), ("I_HAND", "a clawed green monster hand reaching to grab"), ("I_SELL", "a small pile of gold coins with a curved arrow (selling)"),
("I_FLAG", "a red rally flag on a pole"), ("I_BOLT", "a yellow lightning bolt"), ("I_HEAL", "a glowing green healing cross with sparkles"),
("I_SUMMON", "a glowing violet summoning circle with a small imp silhouette"), ("I_BRIDGE", "a small wooden plank bridge arch"),
("I_WALL", "a block of grey stone wall with an upward arrow"), ("I_STEEL", "a riveted dark steel wall plate")]),
("towers", 5, 2, [
("I_ARROW", "a small wooden watchtower with an arrow"), ("I_CANNON", "a squat stone tower with a black cannon"), ("I_MAGIC", "a slender dark tower with a glowing purple orb on top"),
("I_RUNE", "a dark stone obelisk with a glowing red rune and bronze bands"), ("I_DRAWBRIDGE", "a raised wooden drawbridge with chains over water"),
("I_RAM", "a battering ram: a tree trunk with an iron cap under a small wooden roof on wheels"), ("I_CATAPULT", "a wooden catapult with a stone in its arm"),
("I_ORDERS", "two crossed swords (attack order)"), ("I_ORDERS+1", "a round shield (guard order)"), ("I_FREEZE", "a glowing pale-blue snowflake (frost spell)")]),
("traps", 5, 2, [
("I_TRAPS", "a floor trap with sharp metal spikes"), ("I_TRAPS+1", "a floor vent puffing a green poison cloud"), ("I_TRAPS+2", "a floor nozzle shooting a jet of fire"),
("I_TRAPS+3", "a big round rolling boulder"), ("I_TRAPS+4", "a brass alarm bell"), ("I_TRAPS+5", "a glowing red rune mine on the floor"),
("I_TRAPS+6", "a floor trap with a glowing blue frost crystal"), ("I_TRAPS+7", "a wooden door with iron hinges"), ("I_TRAPS+8", "a heavy iron door with rivets"),
("I_TRAPS+9", "a magic door glowing with purple runes")]),
("spells", 4, 2, [
("I_SPELLS", "a glowing all-seeing eye (seer's eye spell)"), ("I_SPELLS+1", "falling rocks from a cracked ceiling (cave-in spell)"),
("I_SPELLS+2", "a winged boot (haste spell)"), ("I_SPELLS+3", "a fist of grey stone (stoneskin spell)"), ("I_SPELLS+4", "gold coins raining down (gold rain spell)"),
("I_SPELLS+5", "a fading translucent ghostly figure (invisibility spell)"), ("I_SPELLS+6", "a green skull in a sickly cloud (plague spell)"),
("I_SPELLS+7", "cracked shaking ground with rocks jumping (quake spell)")]),
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

+125
View File
@@ -0,0 +1,125 @@
# Pit Imp - generated pixel sprite (test for bigger sprites)
Generated on 2026-11-07 with fal.ai, as a test whether creature sprites bigger than the current
10x10 can come from an image model. The description follows the game's imp (`EK::Worker`,
`sprite_rows` in `src/gfx.rs`): orange skin, black outline, yellow eyes, dark-red tunic, pickaxe.
## Step 1: sprite sheet
- Model: `fal-ai/nano-banana-pro`
- Settings: `aspect_ratio: 21:9`, `resolution: 2K`, `num_images: 2`, `output_format: png`
- Results: `raw-1.png` (used: the lower row, compact chibi proportions), `raw-2.png` (more
detailed, too fine for small sprites)
Prompt:
```text
Pixel art sprite sheet for a dark-fantasy dungeon strategy game: the "Pit Imp", a small goblin-like worker demon. One single character shown five times in one horizontal row, evenly spaced, same size, same pose (standing, idle), each view in its own equal cell:
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
The imp: short and stocky, big head, oversized pointed ears and two small black horns, orange skin, glowing yellow eyes, a wide mischievous grin with tiny fangs, a ragged dark-red tunic with a rope belt, bare feet with claws, carrying a small iron pickaxe with a wooden handle over its right shoulder. Thin tail.
Style: crisp retro pixel art at about 32x32 pixels per character, scaled up with hard square pixels, a one-pixel near-black outline around the figure, limited palette of about 12 colours, flat shading with one highlight and one shadow tone, no anti-aliasing, no gradients, no blur. Flat pure magenta background (#FF00FF) everywhere, no ground, no shadows, no text, no labels, no grid lines.
```
## Step 2: pixel grid and palette
`tools/pixelize_sprite.py` (needs Pillow) removes the magenta background, cuts out the five
views, samples them down to a fixed grid (one scale for all views, standing on the bottom) and
reduces them to one shared palette:
```bash
python3 tools/pixelize_sprite.py raw-1.png imp --views 5 --size 32 --colors 16 --crop 0,0.27,1,0.72
```
Result: `imp-32.png` (views front, front 3/4, side, back 3/4, back; transparent) and the 8x
preview `imp-32-preview.png` (a 24x24 test lost most of the face and was dropped). The final
idle sheet was made again with the body scale of the animation (see below).
## Findings
- 32x32 keeps the face (eyes, grin) and the silhouette readable; 24x24 already loses most of the face.
- The pickaxe's iron mostly turns dark; small details would need a manual touch-up.
- In the high-res graphics style the game uses `imp-32.png` (idle) and `imp-hack-32.png` (digging).
## Digging animation (2026-11-07)
Four frames per view (wind-up, swing, impact, recover), made with `fal-ai/nano-banana-pro/edit`
(`resolution: 2K`, `num_images: 2`, `output_format: png`).
1. Front, front three-quarter and side: reference image = the lower row of `raw-1.png` (scaled
to half size), `aspect_ratio: 4:5`. Used: `hack-raw-1.png`, rows 1-3 (its rows 4-5 showed
the front instead of the back in some frames; the second result was dropped).
```text
The attached image is a pixel art sprite sheet of the "Pit Imp" in five views (left to right: front, front three-quarter turned right, side facing right, back three-quarter turned away to the right, back).
Create a new pixel art sprite sheet of exactly the same character - same proportions, colours, outline, pixel size, tunic, horns, ears, tail and the same small iron pickaxe - showing a looping digging animation: the imp swings the pickaxe with both hands and hacks at a rock wall in front of it (the wall itself is not shown).
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 pickaxe raised high above and behind the head, body leaning back; 2 swing - the pickaxe coming down over the head, body leaning forward; 3 impact - the pickaxe head struck down low in front of the body at knee height, body bent forward, a few small rock chips flying; 4 recover - pulling the pickaxe back up at chest height, body upright.
The feet stay in place in all four frames; only arms, pickaxe, head and upper body move.
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 background (#FF00FF) everywhere, no ground, no shadows, no text, no labels, no frame lines, no grid lines.
```
2. Back three-quarter and back: reference images = the idle sheet and the upper three rows of
`hack-raw-1.png`, `aspect_ratio: 16:9`. Used: `hack-back-raw-2.png` (the first result
was dropped).
```text
The first attached image is a pixel art sprite sheet of the "Pit Imp" in five idle views (front, front three-quarter, side, back three-quarter, back). The second attached image shows the same imp's digging animation (columns: 1 wind-up with the pickaxe raised high above and behind the head, 2 swing with the pickaxe coming down over the head, 3 impact with the pickaxe struck down low in front of the body with rock chips, 4 recover with the pickaxe pulled back up at chest height) for the front, front three-quarter and side views.
Create the same four-frame digging animation for the two views seen FROM BEHIND, where the imp faces away from the viewer and we see the back of its head, its back, the tunic from behind and the tail; the face and eyes are never visible:
Row 1: back three-quarter view, the imp turned away from the viewer towards the upper right, hacking at a wall in front of it (away from the viewer).
Row 2: back view, the imp seen straight from behind, hacking at a wall in front of it (away from the viewer).
Columns (frames, left to right): 1 wind-up - pickaxe raised high above the head, its head visible above the imp's head; 2 swing - pickaxe coming down, partly hidden by the head; 3 impact - the pickaxe is in front of the imp and mostly hidden by its body, only the tip shows low at one side, a few rock chips fly up beside the body; 4 recover - pickaxe pulled back up at shoulder height, its handle visible beside the body.
The feet stay in place in all frames.
Layout: a grid of exactly 2 rows and 4 columns, every figure the same size and the same character size as in the attached sheets, evenly spaced, each in its own equal cell, all standing on the same baseline.
Style: identical to the attached sheets - same character, proportions, colours, outline and pixel size, crisp retro pixel art, hard square pixels, no anti-aliasing, no blur. Flat pure magenta background (#FF00FF) everywhere, no ground, no shadows, no text, no labels, no frame lines, no grid lines.
```
### Pixel grid
All sheets of the imp share one body scale (horns to feet = 30 pixels, measured with
`--measure` on a frame where the pickaxe is below the horns), the feet row 31 and the idle
sheet's palette plus two iron greys:
```bash
T=tools/pixelize_sprite.py
python3 $T raw-1.png imp --views 5 --size 32 --colors 16 --crop 0,0.27,1,0.72 --step 18.43 --feet 31
# per view row of the hack sheets (body heights 438, 441, 399 / 664, 653 source pixels):
python3 $T hack-raw-1.png row0 --grid 1x4 --size 32 --crop 0,0,1,0.2 --step 14.6 --feet 31 \
--palette imp-32.png --add-colors "150,152,168;104,106,122" --outline-weight 0.6
# ... rows 1-2 of hack-raw-1.png, rows 0-1 of hack-back-raw-2.png likewise,
# then the five rows stacked into imp-hack-32.png (4 frames x 5 views)
```
`imp-hack-32-preview.png`: the frames, the idle views in the right column for comparison.
The game copies `imp-32.png` and `imp-hack-32.png` to `assets/sprites/`.
### Sack
The gold sack is not generated: it is a small hand-made pixel pattern (`SACK_HD`, `SACK_SMALL`
in `src/gfx.rs`) that the game places on every frame when the atlas is built.
## 64x64 (2026-11-07)
The high-res style shows the imp in 64x64 when close and in 32x32 at the normal zoom (both from
the same raw sheets; a 64x64 sprite shown smaller would lose pixels and flicker). The 64x64
sheets use the body scale 60 pixels, the feet row 63, 24 colours plus the iron greys:
```bash
python3 $T raw-1.png imp --views 5 --size 64 --colors 24 --crop 0,0.27,1,0.72 --step 9.217 --feet 63 \
--add-colors "150,152,168;104,106,122"
# hack rows: --size 64 --step <body height>/60 --feet 63 --palette imp-64.png --outline-weight 0.6
```
Results: `imp-64.png`, `imp-hack-64.png`, preview `imp-hack-64-preview.png` (idle views in the
right column). The game copies them to `assets/sprites/`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

+62
View File
@@ -0,0 +1,62 @@
# Bestiary portraits (fal.ai)
Generated on 2026-11-07/08: one portrait per creature kind (the 30 of
`../sprites/manifest.json` plus the imp, `EK::Worker`) for the bestiary in the high-res style.
Bust (head and shoulders; head and front body for beasts, the machine for the siege engines),
three-quarter view, torch light from one side, dark dungeon wall behind.
- `<id>-raw.png`: the model output, 1024x1024 (local only, ignored by git).
- `<id>-128.png`: the processed 128x128 pixel-art portrait (48 colours, opaque).
- `../../../assets/portraits/portraits.png`: all 31 in one atlas, 8 columns x 4 rows of 128x128,
in the order of `../../../assets/portraits/order.txt` (`EK` names; mirrored by `ORDER` in
`src/portraits.rs`, a test checks both agree).
## Generation
- Model: `fal-ai/nano-banana-pro/edit` (POST https://fal.run/fal-ai/nano-banana-pro/edit)
- `aspect_ratio: 1:1`, `resolution: 1K`, `num_images: 1`, `output_format: png`
- Reference image (`image_urls`, data URI): the front and front three-quarter view of the
creature's idle sheet `../sprites/<id>-64.png` (the imp: `assets/sprites/imp-64.png`),
scaled 6x nearest onto a flat dark grey (768x384).
- 31 images, no retries (every one showed the right character, no text). About $4.65.
- Scripts in `scripts/`: `portraits.py` (descriptions taken from
`../sprites/scripts/creatures.py` plus the imp, framing per kind, prompt template), `gen.py`
(requests, 4 at a time; reads the key from `~/.config/dungeoncraft/fal-key`), `process.py`.
- The first two (knight, spider) were sent before "(the torch itself outside the picture)" was
added; they show a torch.
### Prompt template
`{thing}` is `character`, `creature` (beasts: spider, bat, owl, wyrm, beetle, cavebear,
gargoyle) or `siege engine` (ram, catapult); `{framing}` is `a head-and-shoulders bust
portrait`, `a close-up of its head and the front of its body (bust portrait)` or `a close-up of
the front part of the machine with its crew-less wooden frame and iron parts`.
```text
The attached image shows a small pixel art game sprite of the "{name}" (left: front view, right: three-quarter view). {desc}
Paint a PORTRAIT of exactly this {thing} for the illustrated monster compendium of a dark-fantasy dungeon strategy game: {framing}, three-quarter view turned slightly to the side, looking at the viewer, filling most of the square picture. It must clearly be the same {thing} as in the sprite: the same colours, clothing, armour, markings, gear and weapon (show the weapon or tool where it fits in the frame), but drawn with much more detail and slightly more natural, characterful proportions.
Style: hand-painted pixel art, like a detailed 128x128 pixel portrait scaled up with hard square pixels, a limited colour palette, clean dark outlines, no anti-aliasing blur, no smooth gradients, no photo realism.
Lighting and background: a little dramatic - a warm torch light from one side (the torch itself outside the picture) and a deep shadow on the other side, a rim light on the silhouette. Dark atmospheric dungeon background: rough stone wall in darkness, a faint glow and drifting dust or mist, darkening to near black at the edges.
Exactly one {thing}, no text, no letters, no name, no labels, no frame, no border, no signature, no UI.
```
## Notes per creature
All usable as generated. Weaker in the "close-up" sense: the ram and the catapult show the
whole machine rather than a close-up; the giant is a plain bald man with a club (as the sprite).
The cave dwarf in the bestiary uses the dwarf miner's portrait (as it uses its sprite).
## Processing
```bash
/tmp/sprvenv/bin/python -I scripts/gen.py # all, or: gen.py <id> ...
/tmp/sprvenv/bin/python -I scripts/process.py
```
`process.py`: whole picture (optional per-id crop fractions in `CROP`), box downscale to
128x128, sharpness 1.6, quantize to 48 colours (max coverage, k-means 4, no dithering), RGB;
writes `<id>-128.png`, the atlas and `order.txt`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
# Generate bestiary portraits with fal-ai/nano-banana-pro/edit; the reference image is the
# front and front three-quarter view of the creature's 64 px idle sheet, scaled up 6x.
import base64, io, json, os, sys, urllib.request, concurrent.futures as cf
from PIL import Image
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from portraits import CREATURES, prompt_for, sheet
OUT = "/home/zink/Projects/dungeoncraft/art/generated/portraits"
KEY = open(os.path.expanduser("~/.config/dungeoncraft/fal-key")).read().strip()
EXTRA = os.environ.get("EXTRA", "")
def ref_uri(cid):
im = Image.open(sheet(cid)).convert("RGBA")
h = im.height
two = im.crop((0, 0, 2 * h, h)).resize((12 * h, 6 * h), Image.NEAREST)
bg = Image.new("RGBA", two.size, (40, 36, 44, 255))
bg.alpha_composite(two)
buf = io.BytesIO()
bg.convert("RGB").save(buf, "PNG")
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
def gen(cid, suffix=""):
body = {"prompt": prompt_for(cid, EXTRA), "image_urls": [ref_uri(cid)], "aspect_ratio": "1:1",
"resolution": "1K", "num_images": 1, "output_format": "png"}
req = urllib.request.Request("https://fal.run/fal-ai/nano-banana-pro/edit", data=json.dumps(body).encode(),
headers={"Authorization": "Key " + KEY, "Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=600) as r:
res = json.load(r)
with urllib.request.urlopen(res["images"][0]["url"], timeout=300) as r:
data = r.read()
path = f"{OUT}/{cid}-raw{suffix}.png"
open(path, "wb").write(data)
return cid, "ok " + path
except Exception as e:
msg = str(e)
if hasattr(e, "read"):
try:
msg += " " + e.read().decode()[:300]
except Exception:
pass
return cid, "ERROR " + msg
if __name__ == "__main__":
ids = sys.argv[1:] or [c["id"] for c in CREATURES]
suffix = os.environ.get("SUFFIX", "")
with cf.ThreadPoolExecutor(4) as ex:
for cid, st in ex.map(lambda i: gen(i, suffix), ids):
print(cid, st, flush=True)
@@ -0,0 +1,43 @@
# Portrait prompts for the bestiary (high-res style). Descriptions come from the sprite pipeline.
import importlib.util, os
ROOT = "/home/zink/Projects/dungeoncraft"
_spec = importlib.util.spec_from_file_location("creatures", f"{ROOT}/art/generated/sprites/scripts/creatures.py")
_cr = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_cr)
IMP = dict(id="imp", kind="Worker", name="Pit Imp",
desc="A small goblin-like worker demon: big head, oversized pointed ears and two small black horns, orange skin, glowing yellow eyes, a wide mischievous grin with tiny fangs, a ragged dark-red tunic with a rope belt, carrying a small iron pickaxe with a wooden handle over its right shoulder.")
CREATURES = [IMP] + [dict(id=c["id"], kind=c["kind"], name=c["name"], desc=c["desc"]) for c in _cr.CREATURES]
BY_ID = {c["id"]: c for c in CREATURES}
SIEGE = {"ram", "catapult"}
BEAST = {"spider", "bat", "owl", "wyrm", "beetle", "cavebear", "gargoyle"}
def sheet(cid):
if cid == "imp":
return f"{ROOT}/assets/sprites/imp-64.png"
return f"{ROOT}/art/generated/sprites/{cid}-64.png"
TEMPLATE = """The attached image shows a small pixel art game sprite of the "{name}" (left: front view, right: three-quarter view). {desc}
Paint a PORTRAIT of exactly this {thing} for the illustrated monster compendium of a dark-fantasy dungeon strategy game: {framing}, three-quarter view turned slightly to the side, looking at the viewer, filling most of the square picture. It must clearly be the same {thing} as in the sprite: the same colours, clothing, armour, markings, gear and weapon (show the weapon or tool where it fits in the frame), but drawn with much more detail and slightly more natural, characterful proportions.
Style: hand-painted pixel art, like a detailed 128x128 pixel portrait scaled up with hard square pixels, a limited colour palette, clean dark outlines, no anti-aliasing blur, no smooth gradients, no photo realism.
Lighting and background: a little dramatic - a warm torch light from one side (the torch itself outside the picture) and a deep shadow on the other side, a rim light on the silhouette. Dark atmospheric dungeon background: rough stone wall in darkness, a faint glow and drifting dust or mist, darkening to near black at the edges.
Exactly one {thing}, no text, no letters, no name, no labels, no frame, no border, no signature, no UI."""
def prompt_for(cid, extra=""):
c = BY_ID[cid]
if cid in SIEGE:
thing, framing = "siege engine", "a close-up of the front part of the machine with its crew-less wooden frame and iron parts"
elif cid in BEAST:
thing, framing = "creature", "a close-up of its head and the front of its body (bust portrait)"
else:
thing, framing = "character", "a head-and-shoulders bust portrait"
p = TEMPLATE.format(name=c["name"], desc=c["desc"], thing=thing, framing=framing)
return p + ("\n\n" + extra if extra else "")
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# Raw portraits (1024x1024) -> 128x128 pixel-art portraits (box downscale, 48 colours, no
# dithering, opaque) and the atlas assets/portraits/portraits.png (8 columns) + order.txt.
import os, sys
from PIL import Image, ImageEnhance
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from portraits import CREATURES
ROOT = "/home/zink/Projects/dungeoncraft"
D = f"{ROOT}/art/generated/portraits"
OUT = f"{ROOT}/assets/portraits"
SIZE, COLORS, COLS = 128, 48, 8
CROP = {} # id -> (l, t, r, b) fractions, default the whole picture
os.makedirs(OUT, exist_ok=True)
rows = (len(CREATURES) + COLS - 1) // COLS
atlas = Image.new("RGB", (COLS * SIZE, rows * SIZE), (0, 0, 0))
order = []
for i, c in enumerate(CREATURES):
im = Image.open(f"{D}/{c['id']}-raw.png").convert("RGB")
l, t, r, b = CROP.get(c["id"], (0, 0, 1, 1))
w, h = im.size
im = im.crop((round(l * w), round(t * h), round(r * w), round(b * h)))
im = im.resize((SIZE, SIZE), Image.BOX)
im = ImageEnhance.Sharpness(im).enhance(1.6)
im = im.quantize(colors=COLORS, method=Image.Quantize.MAXCOVERAGE, kmeans=4, dither=Image.Dither.NONE).convert("RGB")
im.save(f"{D}/{c['id']}-{SIZE}.png", optimize=True)
atlas.paste(im, ((i % COLS) * SIZE, (i // COLS) * SIZE))
order.append(c["kind"])
atlas.save(f"{OUT}/portraits.png", optimize=True)
open(f"{OUT}/order.txt", "w").write("\n".join(order) + "\n")
print(atlas.size, os.path.getsize(f"{OUT}/portraits.png"), "bytes")
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+38
View File
@@ -0,0 +1,38 @@
# Soul core heart - generated 3D model
Generated on 2026-11-07 with fal.ai. The prompt was written from the game's procedural soul core
(`draw_throne` and `draw_heart` in `src/render3d.rs`): an obsidian stepped throne, four curved spikes
with bone tips, iron chains holding a beating crystal heart, and glowing veins.
## Step 1: concept image
- Model: `fal-ai/flux-2-pro`
- Settings: `image_size: square_hd` (1024x1024), `output_format: png`, `seed: 4711`
- Result: `concept.png`
Prompt:
```text
A single dark-fantasy game prop, the "soul core" of an evil dungeon master, isometric three-quarter front view, full object visible and centered, on a plain neutral light-grey studio background, no floor, no scenery, no text.
The prop: a squat stepped pyramid throne of polished black-purple obsidian, four square tiers getting smaller towards the top. At each of the four corners of the base stands a short obsidian pillar, and from each pillar a thick curved obsidian spike rises and leans slightly outwards, its tip capped with yellowed bone. From near the top of every spike a heavy dark iron chain sags inwards and holds a floating heart.
The heart: a faceted crystal heart of deep blood-red ruby, classic heart shape with two rounded lobes and a pointed bottom, hovering about one heart-height above the top tier, held in the air only by the four chains. It glows from inside with hot red-orange light, a few brighter facets catch the light, faint embers drift upwards from it. Thin glowing red veins run across the obsidian steps from below the heart.
Style: stylized low-to-mid poly game asset, chunky readable silhouette, clean shapes, hand-painted textures, like a strategy game building seen from above. Even soft lighting, sharp focus, the whole prop fits in the frame with margin.
```
## Step 2: image to 3D
- Model: `fal-ai/hunyuan-3d/v3.1/pro/image-to-3d`
- Input: `concept.png` (front view only, no extra views)
- Settings: `generate_type: Normal` (textured), `face_count: 100000`, `enable_pbr: false`
- Result: `soul-core-heart.glb` (about 20 MB, texture baked in; not used by the game yet), preview `preview.png`
No text prompt is used in this step. The model only sees the concept image.
## Notes
- The throne, the chains and the heart are one fused mesh. The heart cannot be animated on its own;
for a beating heart, generate it as a separate model (see below).
- In the concept the heart rests on a small glowing pedestal instead of floating freely.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.
+401
View File
@@ -0,0 +1,401 @@
# Creature sprites in the imp's style (fal.ai)
Generated on 2026-11-07: one idle sprite sheet per creature kind of `SPRITE_KINDS` in
`src/gfx.rs` (all except `EK::Worker`, the imp, see `../imp-sprite/`), in the style of the imp's
`raw-1.png` lower row. Each creature has `<id>-32.png` (160x32) and `<id>-64.png` (320x64): five
views side by side (front, front three-quarter turned right, side facing right, back
three-quarter turned away to the right, back), transparent, one palette per sheet, feet (or the
lowest pixel) on the bottom row, the tallest or widest view filling the cell. Ids are the
`unit.<id>` keys; `manifest.json` maps them to the `EK` variants with a quality note.
## Generation
- Model: `fal-ai/nano-banana-pro/edit` (POST https://fal.run/fal-ai/nano-banana-pro/edit)
- `aspect_ratio: 21:9`, `resolution: 2K`, `num_images: 1`, `output_format: png`
- Reference image (`image_urls`, as a data URI): `style-ref.png` = the lower row of
`../imp-sprite/raw-1.png` (crop fractions 0,0.27,1,0.72) scaled to 1600 px wide, the dark
cell frame lines painted over with magenta (the kobold, the first request, still had a
reference with frame lines and copied them).
- Background: flat magenta, flat green (#00FF00) for the purple creatures (spider, mage, bat, trader).
- 34 images: 30 creatures + 4 retries (spider, dwarf, revenant, ram; each had the wrong
number or order of views). For dwarf and ram the retry gave the missing back three-quarter
view but lost another one, so both tries are combined. About $5 at ~$0.15 per 2K image.
- Scripts in `scripts/`: `creatures.py` (descriptions, prompt template), `gen.py` (requests,
4 at a time; reads the key from `~/.config/dungeoncraft/fal-key`), `compose.py` (sets chosen
figures of one or more raw sheets into one new row), `process.py` (runs the pixelize tool for
all creatures with the arguments of `extra.txt`).
Even with "exactly five figures in one row" in the prompt the model often drew two rows (mostly
two copies of the five views, then the top row is used with `--crop 0,0,1,0.5`) or skipped
the back three-quarter view.
### Prompt template
`{thing}` is `creature` or `siege engine`; `{pose}` is `standing, idle`, `hovering in the air,
idle` (bat, gargoyle, owl, wyrm, fairy) or `standing still` (ram, catapult); `{bgname}` is
`magenta (#FF00FF)` or `green (#00FF00)`. The kobold and the first spider request were sent
without "exactly five figures in one row (no second row, no extra figures)," (the layout line
read "the same five views in one single horizontal row,").
```text
The attached image is the style reference: a pixel art sprite sheet of the "Pit Imp" in five views (left to right: front, front three-quarter turned right, side facing right, back three-quarter turned away to the right, back).
Draw a DIFFERENT {thing} in exactly the same style: the "{name}". {desc}
Use exactly the same style as the reference: the same compact chibi proportions (big head, short stocky body, about the same overall size as the imp), the same thick one-pixel near-black outline, the same palette approach (flat colours with one highlight and one shadow tone), the same pixel size (about 32x32 pixels per figure, scaled up with hard square pixels), no anti-aliasing, no gradients, no blur.
Layout: exactly five figures in one single horizontal row (no second row, no extra figures), the same five views, in the same order (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), evenly spaced, all the same size, {pose}, each figure separated from its neighbours by empty background.
Background: flat pure {bgname} everywhere, no ground, no shadows, no text, no labels, no frames, no grid lines, no cell borders.
```
Retries (dwarf, revenant) appended:
```text
Important: there must be exactly FIVE figures in ONE row. Do not skip the fourth one: the back three-quarter view, seen from behind at an angle, the face hidden, the body turned away from the viewer towards the upper right, between the side view and the straight back view.
```
The ram retry appended instead:
```text
Important: there must be exactly FIVE drawings of the machine in ONE row, no second row. 1 front view: the iron-capped end of the ram points straight at the viewer. 2 front three-quarter view: the capped end points towards the viewer and to the right. 3 side view: the capped end points to the right. 4 back three-quarter view: the capped end points away from the viewer towards the upper right, we see the rear end of the machine. 5 back view: we see the rear end straight on.
```
## Pixel grid
`tools/pixelize_sprite.py` (Pillow: `/tmp/sprvenv/bin/python -I`), from the repo root:
```bash
T=tools/pixelize_sprite.py
python $T art/generated/sprites/<id>-raw.png art/generated/sprites/<id> --views 5 --size 32 --colors 16 --feet 31 --outline-weight 0.6 [extra]
python $T art/generated/sprites/<id>-raw.png art/generated/sprites/<id> --views 5 --size 64 --colors 24 --feet 63 --outline-weight 0.6 [extra]
```
(the tool also writes `<id>-<size>-preview.png`; those were deleted). `[extra]` per creature is
listed below. Composed raws were made first with `scripts/compose.py` (run from `scripts/`).
### Tool changes (backwards compatible)
- Background key `--bg auto` (default): the key colours are sampled from the sheet's border and
only colours close to them (and their blends with the dark outline) are removed, so purple
creatures on green survive; `--bg magenta` is the old fixed test, `--bg R,G,B` an explicit key.
- Blends of the key colour with thin figure parts (pinkish edge pixels on a spear) get no vote.
- Frame/grid lines (a column or row at least 97% one flat non-background colour) are removed,
plus their anti-aliased edges.
- `--split auto` (default): figures are found by the empty columns between them, else by
connected parts, else by cutting touching figures at their thinnest column, else equal cells
(`--split cells`, the old behaviour); small pieces of a neighbour at a cell edge are dropped.
- `--row-counts 4,3` (rows with different numbers of figures) and `--pick 0,1,2,m5,3` (build
the output row from chosen figures, `m` mirrors).
- Single pixels without any neighbour are removed (`--no-despeckle` keeps them).
- The imp's `raw-1.png` gives the same sheet as before (42 of 5120 pixels differ in coverage).
## Creatures
### kobold - Kobold (`EK::Kobold`)
Description (standing, magenta background): A small scaly lizard-folk warrior: short and wiry, big lizard-like head with a short snout, bright green scaly skin, glowing yellow eyes, a toothy eager grin with sharp little teeth, a simple dark-green tunic, clawed bare feet, a short tail. It holds an upright wooden spear with a steel tip in its right hand.
Raw: kobold-raw.png
Extra pixelize arguments: none
Result: ok - good - chibi lizard, spear in all views; raw has frame lines (removed by the tool)
### spider - Cave Spider (`EK::Spider`)
Description (standing, green background): A big dungeon spider: a big round body and head in dark violet-purple with lighter purple highlights, a cluster of glowing yellow eyes on the front of the head, eight thick black jointed legs spread around it, small fangs. Low to the ground, compact and menacing, cute-chibi proportions with an oversized head.
Raw: spider-raw-try1.png (2 rows of 3, dropped), spider-raw.png (retry, 2 rows of 4, used)
Extra pixelize arguments: `--grid 2x4 --pick 0,1,2,3,3`
Result: ok - usable - both tries came as 2 rows without a back three-quarter view; the back view is used twice (views 4 and 5 identical)
### mage - Shadow Mage (`EK::Mage`)
Description (standing, green background): A hooded dark sorcerer: a big pointed purple hood that hides most of the oversized head, the face in shadow with only a pale chin and two glowing yellow eyes visible, dark-purple robes with light violet trim down to the feet, a wooden staff with a glowing yellow gem on top held in the right hand.
Raw: mage-raw.png
Extra pixelize arguments: none
Result: ok - good - green key, purple hood and robe survive, glowing eyes, gem staff
### ogre - Stone Ogre (`EK::Ogre`)
Description (standing, magenta background): A hulking ogre with skin like rock: broad and hunched, almost no neck, skin of grey stone with darker grey cracks and patches, small glowing yellow eyes, a heavy jaw with an underbite, enormous arms and fists reaching low, short thick legs, a ragged brown loincloth. No weapon - it fights with its fists. Chibi-stocky, as wide as it is tall.
Raw: ogre-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - stocky stone ogre; raw has 2 rows (top row used); back three-quarter drawn almost as a side view
### knight - Knight (`EK::Knight`)
Description (standing, magenta background): A heavily armoured knight of the holy Order: a big polished silver great helm with a dark visor slit, silver shoulder plates and greaves, a blue tabard with a yellow sun emblem on the chest over chainmail, dark boots, a short straight steel sword held upright in the right hand.
Raw: knight-raw.png
Extra pixelize arguments: none
Result: ok - good - great helm, blue tabard with sun, sword
### archer - Archer (`EK::Archer`)
Description (standing, magenta background): A hero archer of the Order: a little green hat with a peak, a friendly human face with brown hair, a green tunic with a brown belt, brown trousers and leather boots, a quiver of arrows on the back, holding a wooden bow upright in the left hand.
Raw: archer-raw.png
Extra pixelize arguments: none
Result: ok - good - a bit taller than the imp, bow and quiver readable
### priest - Priest (`EK::Priest`)
Description (standing, magenta background): A healing priest of the Order: a human with short brown hair and a kind face, a long plain white robe down to the feet with a golden sun emblem on the chest, a small golden halo ring floating above the head, hands folded in prayer.
Raw: priest-raw.png
Extra pixelize arguments: none
Result: ok - good - white robe, sun emblem, floating halo
### dwarf - Dwarf Miner (`EK::Dwarf`)
Description (standing, magenta background): A stout dwarf miner of the Order: very short and broad, a grey steel helmet, a big bushy orange beard, a skin-coloured nose and eyes peeking out, a brown leather jerkin and belt, brown trousers and boots, a steel pickaxe with a wooden handle over the right shoulder.
Raw: dwarf-raw-try1.png, dwarf-raw-try2.png (retry) -> composed dwarf-raw.png
Compose: `python compose.py art/generated/sprites/dwarf-raw.png art/generated/sprites/dwarf-raw-try1.png:1:4:0 art/generated/sprites/dwarf-raw-try1.png:1:4:1 art/generated/sprites/dwarf-raw-try1.png:1:4:2 art/generated/sprites/dwarf-raw-try2.png:1:4:3 art/generated/sprites/dwarf-raw-try1.png:1:4:3`
Extra pixelize arguments: none
Result: ok - good - composed from two tries (each had only 4 views): try 1 front/front 3/4/side/back + try 2 back three-quarter
### paladin - Paladin of Light (`EK::Paladin`)
Description (standing, magenta background): A big glowing holy champion: a golden helmet with a dark visor, shining white plate armour with golden trim and a golden sun on the chest, golden greaves, broad shoulders, a white holy sword with a golden hilt held upright in the right hand. Bulkier than the other characters but still chibi.
Raw: paladin-raw.png
Extra pixelize arguments: none
Result: ok - good - white/gold armour, big sword; very bright
### jailer - Jailer (`EK::Jailer`)
Description (standing, magenta background): A big quiet dungeon jailer: broad-shouldered and heavy, a grey leather executioner's hood with eye holes and glowing yellow eyes, grey-skinned bare muscular arms, a dark-red leather apron over the chest and belly, heavy boots, a coiled iron chain hanging from the right hand.
Raw: jailer-raw.png
Extra pixelize arguments: none
Result: ok - good - hood, grey arms, red apron, chain
### priestess - Flame Priestess (`EK::Priestess`)
Description (standing, magenta background): A fire priestess of the dungeon: a woman with fair skin, flame-orange hair rising like fire, a long dark-red robe to the feet with an orange flame emblem on the chest, a small burning fireball floating above her raised right hand.
Raw: priestess-raw.png
Extra pixelize arguments: none
Result: ok - good - flame hair, red robe, fireball
### chainbreaker - Chainbreaker (`EK::Chainbreaker`)
Description (standing, magenta background): A legendary obsidian demon warrior: a muscular demon of black obsidian with glowing orange-lava cracks across the body, glowing yellow eyes, short curved horns, broken iron shackles with dangling chain links on both wrists, a steel battle axe with a light grey blade in each hand, heavy clawed feet.
Raw: chainbreaker-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - obsidian with lava cracks, horns, axes; raw has 2 rows (top row used); dark at 32 px
### bat - Giant Bat (`EK::Bat`)
Description (hovering, green background): A big dungeon bat: a round furry dark-purple body with lighter purple highlights, leathery purple wings spread out with black finger bones, big pointed ears, small glowing red eyes, a little fanged mouth, tiny clawed feet hanging down. Hovering in the air, wings spread but kept compact within its own cell.
Raw: bat-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - green key, purple body and wings survive; raw has 2 rows (top row used)
### gargoyle - Chain Gargoyle (`EK::Gargoyle`)
Description (hovering, magenta background): A winged stone gargoyle: a crouching, hunched creature of carved grey stone with darker grey cracks, stone bat wings with pale tips, short curved horns, glowing yellow eyes, a grim mouth with fangs, clawed hands and feet, an iron chain wrapped around one arm. Hovering in the air.
Raw: gargoyle-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - grey stone, wings, chain; raw has 2 rows (top row used)
### owl - Arcane Owl (`EK::Owl`)
Description (hovering, magenta background): A wise magical owl: a round brown-feathered owl with lighter brown and cream breast feathers, tufted ear feathers, huge glowing yellow eyes with black pupils, a small yellow beak, wings with deep blue magical tips, yellow talons, a judging expression. Hovering in the air with its wings half spread.
Raw: owl-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - round chibi owl, blue wing tips; raw has 2 rows (top row used)
### wyrm - Ember Wyrm (`EK::Wyrm`)
Description (hovering, magenta background): A small fire dragon: a compact red dragon with dark-red scales on the back, a glowing orange belly, dark-red bat wings, a single white horn, glowing yellow eyes, a curled tail, clawed legs, small embers of fire at its mouth. Hovering in the air.
Raw: wyrm-raw.png
Extra pixelize arguments: none
Result: ok - good - red dragon, orange belly, white horn; tall and thin because of the hanging tail
### cogling - Cogling (`EK::Cogling`)
Description (standing, magenta background): A sooty little goblin tinkerer: tiny and round, burnt-orange sooty skin, big brass goggles with glowing yellow lenses over the eyes, a grey work overall over an orange shirt, small boots, a steel hammer held up in the right hand, a few small brass cogs on the belt.
Raw: cogling-raw.png
Extra pixelize arguments: `--crop 0,0,1,0.5`
Result: ok - good - goggles, overall, hammer; raw has 2 rows (top row used)
### revenant - Revenant (`EK::Revenant`)
Description (standing, magenta background): An undead warrior: a gaunt pale corpse with a bony white skull face, hollow eye sockets with glowing green grave-light, tattered grey rags and old rusty armour pieces, thin bony arms and legs, a rusty steel sword held in the right hand.
Raw: revenant-raw-try1.png (4 views, dropped), revenant-raw.png (retry, used)
Extra pixelize arguments: none
Result: ok - good - second try (first had only 4 views); big skull, green eyes, rusty sword
### giant - Giant (`EK::Giant`)
Description (standing, magenta background): A hero giant of the Order: a big broad human brute with fair skin, a small bald head with a dumb frown and small eyes, a brown leather jerkin over a blue shirt, bare muscular arms, heavy brown boots, a big wooden tree-trunk club resting on the right shoulder. Bulkier than the other characters but still chibi.
Raw: giant-raw.png
Extra pixelize arguments: none
Result: ok - good - bald brute with club; drawn chibi, so not much bigger than the others (the game scales it)
### fairy - Fairy Spy (`EK::Fairy`)
Description (hovering, magenta background): A tiny glowing fairy informer: a small figure with fair skin, blonde hair and small pointed ears, glowing yellow eyes, a short blue dress, two pairs of translucent pale-blue dragonfly wings, a few golden sparkles below the feet. Hovering in the air.
Raw: fairy-raw.png
Extra pixelize arguments: none
Result: ok - good - blue dress, pale wings; a few gold sparkles below the feet are part of the sprite, so the bottom row is the sparkles, not the feet
### battlemage - Battle Mage (`EK::BattleMage`)
Description (standing, magenta background): A fire mage of the Order: a tall pointed blue wizard hat, a human face with a stern look and a short grey beard, long blue robes with white trim and a white sash, a wooden staff in the right hand with a burning orange-yellow flame on top.
Raw: battlemage-raw.png
Extra pixelize arguments: none
Result: ok - good - blue hat and robes, burning staff, grey beard
### ram - Battering Ram (`EK::Ram`)
Description (siege engine, magenta background): A wooden siege battering ram, no people visible: a compact wooden frame on four solid wooden wheels with a slanted brown plank roof; under the roof hangs a thick tree trunk with a big grey iron cap on its front end. Brown and dark-brown wood, iron rivets. Chunky toy-like proportions. Show the same five views of the machine (front, front three-quarter turned right, side facing right, back three-quarter, back).
Raw: ram-raw-try1.png, ram-raw-try2.png (retry) -> composed ram-raw.png
Compose: `python compose.py art/generated/sprites/ram-raw.png art/generated/sprites/ram-raw-try1.png:2:3,3:0 art/generated/sprites/ram-raw-try1.png:2:3,3:2 art/generated/sprites/ram-raw-try1.png:2:3,3:1 art/generated/sprites/ram-raw-try2.png:2:3,2:4 art/generated/sprites/ram-raw-try1.png:2:3,3:5`
Extra pixelize arguments: none
Result: ok - usable - composed from two tries (both 2 rows, views out of order): try 1 front/front 3/4/side/back + try 2 back three-quarter; the side view is wide, the 'back' view of try 1 may show the front end again
### catapult - Catapult (`EK::Catapult`)
Description (siege engine, magenta background): A wooden siege catapult, no people visible: a low rectangular wooden base on four wooden wheels, a wooden throwing arm angled upward with a cup at its tip holding a grey stone boulder, ropes and a crossbeam. Brown and dark-brown wood, iron fittings. Chunky toy-like proportions. Show the same five views of the machine (front, front three-quarter turned right, side facing right, back three-quarter, back).
Raw: catapult-raw-orig.png -> composed catapult-raw.png
Compose: `python compose.py art/generated/sprites/catapult-raw.png C:2:4,3:0 C:2:4,3:1 C:2:4,3:2 C:2:4,3:3 C:2:4,3:6::0.8 # C = art/generated/sprites/catapult-raw-orig.png`
Extra pixelize arguments: none
Result: ok - usable - raw came as 2 rows of 4+3; front/front 3/4/side/back 3/4 from the top row, back view from row 2 (scaled 0.8); noisy at 32 px
### cavebear - Cave Bear (`EK::CaveBear`)
Description (standing, magenta background): A shaggy cave bear: big, round and shaggy, thick dark-brown fur with lighter brown highlights, small round ears, small glowing yellow eyes, a light tan muzzle with a black nose, big paws with claws, standing on all four legs, grumpy. Chunky chibi proportions with a big head.
Raw: cavebear-raw.png
Extra pixelize arguments: `--grid 2x4 --row-counts 4,2 --pick 0,1,2,m5,3`
Result: ok - good - four-legged bear; raw has 2 rows of 4+2; back three-quarter is a mirrored figure of row 2
### beetle - Giant Beetle (`EK::Beetle`)
Description (standing, magenta background): A beetle the size of a barrel: a big round shiny dark-green shell with lighter green highlights and a line down the middle, a dark head with glowing yellow eyes, two curved black mandibles and two short antennae, six short black legs. Low on the ground, chunky and round.
Raw: beetle-raw-orig.png -> composed beetle-raw.png
Compose: `python compose.py art/generated/sprites/beetle-raw.png B:2:4,2:0 B:2:4,2:1 B:2:4,2:2 B:2:4,2:5::0.78 B:2:4,2:3 # B = art/generated/sprites/beetle-raw-orig.png`
Extra pixelize arguments: none
Result: ok - good - green shell, yellow eyes; raw has 2 rows of 4+2; back three-quarter from row 2 (scaled 0.78)
### trader - Trader (`EK::Trader`)
Description (standing, green background): A wandering hooded merchant: a deep purple hooded cloak with a friendly human face peeking out, a yellow clasp on the chest, a big brown leather backpack piled high with goods, brown boots, a small glowing lantern held in the right hand.
Raw: trader-raw.png
Extra pixelize arguments: none
Result: ok - good - green key, purple cloak survives, backpack, lantern
### thief - Thief (`EK::Thief`)
Description (standing, magenta background): A sneaky thief: a dark charcoal-grey hood and a black mask over the lower face with only the eyes showing, close-fitting dark-grey clothes with a brown belt and soft brown boots, a short steel dagger in the right hand, a bulging tan sack of gold coins slung over the back.
Raw: thief-raw.png
Extra pixelize arguments: none
Result: ok - good - dark hood and mask, dagger, tan loot sack
### monk - Monk (`EK::Monk`)
Description (standing, magenta background): A fighting monk of the Order: a shaven bald head, calm face with closed eyes, a simple orange robe with darker orange shading, tied with a yellow rope belt, bare arms and bare fists raised in a ready fighting stance, sandals.
Raw: monk-raw.png
Extra pixelize arguments: none
Result: ok - good - bald, orange robe, fists raised
### ranger - Ranger (`EK::Ranger`)
Description (standing, magenta background): A woodsman ranger of the Order: a green hood and green cloak, a human face, a green tunic with a brown leather belt and strap, brown leather trousers and boots, a wooden bow held upright in the left hand, a small iron snare trap hanging from the belt.
Raw: ranger-raw.png
Extra pixelize arguments: none
Result: ok - good - green hood and cloak, bow, snare
### inquisitor - Inquisitor (`EK::Inquisitor`)
Description (standing, magenta background): A frail zealous preacher of the Order: a thin old man with a tall dark-red mitre hat, a pale face with narrowed eyes, long white robes with a red stripe down the middle and red hems, holding open a glowing yellow holy book in front of him.
Raw: inquisitor-raw.png
Extra pixelize arguments: none
Result: ok - good - red mitre, white/red robes, glowing book; looks less frail than described
+151
View File
@@ -0,0 +1,151 @@
# Creature animations (pilot: knight, kobold)
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):
```bash
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):
```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 ../<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
```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 not covered by `.gitignore` (which only ignores
`art/generated/sprites/*-raw*.png`); add `art/generated/sprites/anim/*-raw*.png` before
committing.
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Loaded 100 of 796 files, more files were not shown because too many files have changed in this diff. Show more