Compare commits

...
93 Commits
Author SHA1 Message Date
Richard Zinkanddeepseek-v4.1-flash (opencode-go) via CodeCraft e44aab6f5c Add gamepad support to the desktop build
Co-Authored-By: deepseek-v4.1-flash (opencode-go) via CodeCraft
2026-11-11 15:43:32 +01:00
Richard Zinkanddeepseek-v4.1-flash (opencode-go) via CodeCraft 8d51d6ec6a Add browser gamepad support with pad button pictograms
Co-Authored-By: deepseek-v4.1-flash (opencode-go) via CodeCraft
2026-11-11 14:43:39 +01:00
Richard ZinkandClaude Opus 5.5 073999d592 Bump version to v202611-00079
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 09:22:38 +01:00
Richard ZinkandClaude Opus 5.5 5e8c1ef28c Bump version to v202611-00078
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 09:15:32 +01:00
Richard ZinkandClaude Opus 5.5 a8284ca85c Remains of the dead in one mesh: drawing stays fast with hundreds of them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:20:35 +01:00
Richard ZinkandClaude Opus 5.5 6886b2e1a4 Self test opens the bestiary too; bestiary screenshot with the portraits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:12:33 +01:00
Richard ZinkandClaude Opus 5.5 918458df50 README screenshots in the high-res style
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:11:10 +01:00
Richard ZinkandClaude Opus 5.5 7f57f64947 High-res ores with big outlined lumps that stand out from the earth, kept in their own colours; screenshot tool for the desktop build
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:09:11 +01:00
Richard ZinkandClaude Opus 5.5 48c20fabbc Walk, die and attack animations for all creatures (sheets, prompts and scripts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:00:31 +01:00
Richard ZinkandClaude Opus 5.5 8c5afe872f High-res effects: explosions, smoke, bursts, impact stars, healing and glowing projectiles as flipbooks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:00:20 +01:00
Richard ZinkandClaude Opus 5.5 7972e8ad1b Self test: fast forward and drawing times
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:56:26 +01:00
Richard ZinkandClaude Opus 5.5 7e2e638e05 Brighter obsidian for the soul core thrones, so their stone shows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:29:42 +01:00
Richard ZinkandClaude Opus 5.5 04d9d719d7 Desktop self test: DC_SHOT starts the demo map or a savegame, takes a screenshot and quits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:28:23 +01:00
Richard ZinkandClaude Opus 5.5 d7b6d6de7e Heart sounds: the hurt soul core beats softly, the rival hearts creak, chime and bubble nearby
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:12:08 +01:00
Richard ZinkandClaude Opus 5.5 9800ce7156 Holiday music: a dark carol for Christmas, a spring tune for Easter, a harpsichord dance for Halloween
During a holiday the title music plays its piece, the build music every fourth time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:12:08 +01:00
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
Richard ZinkandClaude Opus 5.5 d8aa0e5fc1 Bump version to v202611-00076
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:33:43 +01:00
Richard ZinkandClaude Opus 5.5 98b78eb518 Desktop release builds: Linux AppImage, Windows exe and macOS app via tools/build_desktop.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:22:11 +01:00
Richard ZinkandClaude Opus 5.5 2475a8ca3c Desktop build plays music and sound through the system sound output; desktop build documented
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:49:45 +01:00
Richard ZinkandClaude Opus 5.5 db521ed2ee Desktop build: settings and progress kept as files, savegames in a saves folder, system file dialog for loading
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:46:11 +01:00
Richard ZinkandClaude Opus 5.5 1df97b4af2 Bump version to v202611-00075
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:08:28 +01:00
Richard ZinkandClaude Opus 5.5 bd3f42b8b8 Hero waves break in where the warning shows: they round the realm to the marked entry; the entry tile glows red
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:08:28 +01:00
Richard ZinkandClaude Opus 5.5 dab31579df Bump version to v202611-00074
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:04 +01:00
Richard ZinkandClaude Opus 5.5 e85e602491 Balance: easy AI stays smaller; balance runs documented
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:03 +01:00
Richard ZinkandClaude Opus 5.5 28eac52953 Memorial stones only for level 10 creatures, at most 30 per map
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:03 +01:00
Richard ZinkandClaude Opus 5.5 66eaedf016 Automated balance runs; AI masters keep growing, mine more, defend with the rally flag and spells; Builder talent stronger
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:07:02 +01:00
Richard ZinkandClaude Opus 5.5 24ba7fb5e3 Bump version to v202611-00073
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:07 +01:00
Richard ZinkandClaude Opus 5.5 beb53a2e0c Serve the wasm and scripts gzip-compressed (packed at build time)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:07 +01:00
Richard ZinkandClaude Opus 5.5 c52fc4c4ae Hall of the fallen: kills and causes of death, in the game and over all games in the profile
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:06 +01:00
Richard ZinkandClaude Opus 5.5 065fe80218 Bump version to v202611-00072
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:37:28 +01:00
Richard ZinkandClaude Opus 5.5 bc1f89b4a7 Sounds for weather, rogues and groves: thunder, rain and wind ambience, coin theft, snares, sermons, spores
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:37:28 +01:00
Richard ZinkandClaude Opus 5.5 340014c582 Bump version to v202611-00071
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:29:44 +01:00
Richard ZinkandClaude Opus 5.5 eb219f6b01 Script blocks for weather and talent experience, groves as ground; the co-op campaign brings rogues and a storm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:29:44 +01:00
Richard ZinkandClaude Opus 5.5 4e3158cc19 Bump version to v202611-00070
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:21:07 +01:00
Richard ZinkandClaude Opus 5.5 70f112543f Interface size setting: auto, large, medium or small on the world's pixel grid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:21:07 +01:00
Richard ZinkandClaude Opus 5.5 95655a0886 Bump version to v202611-00069
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:12:42 +01:00
Richard ZinkandClaude Opus 5.5 5a2ac32071 Command markers only with the debug setting; removing a marker clears them at once
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:12:41 +01:00
Richard ZinkandClaude Opus 5.5 be4459e19e Bump version to v202611-00068
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:40:22 +01:00
Richard ZinkandClaude Opus 5.5 ad5ceb8c13 Named creatures: names per kind and language, epithets at higher levels, memorial stones for the fallen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:40:22 +01:00
Richard ZinkandClaude Opus 5.5 189da6f408 Bump version to v202611-00067
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:19:37 +01:00
Richard ZinkandClaude Opus 5.5 0cbcd1c8d1 Markers: a second click or tap removes them; right click with the flag tool takes the flag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:19:37 +01:00
Richard ZinkandClaude Opus 5.5 c3f7939627 Bump version to v202611-00066
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:11:20 +01:00
Richard ZinkandClaude Opus 5.5 3e17bdf52d Weather above ground: rain puts out fires, fog shortens sight, storms strike with lightning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:11:19 +01:00
Richard ZinkandClaude Opus 5.5 f98f05f0d2 Bump version to v202611-00065
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:48:06 +01:00
Richard ZinkandClaude Opus 5.5 25334e7b17 Portrait and square screens: world pixels, interface and zoom count on the shorter side
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:48:06 +01:00
Richard ZinkandClaude Opus 5.5 16857f1b9d Bump version to v202611-00064
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:25 +01:00
Richard ZinkandClaude Opus 5.5 9804e24015 Smaller touch interface: the same pixel density as on the desktop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:11 +01:00
Richard ZinkandClaude Opus 5.5 bd956a4f57 Master talents: experience, points and four branches (Builder, Warlord, Warlock, Merchant) with a toolbar window
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:11 +01:00
Richard ZinkandClaude Opus 5.5 b93bcd89c4 Bump version to v202611-00063
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:02:50 +01:00
Richard ZinkandClaude Opus 5.5 ff48a69571 New heroes: thief steals gold, monk ignores spells, ranger lays snares, inquisitor turns weak creatures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:02:50 +01:00
Richard ZinkandClaude Opus 5.5 a1a8f9d71d Bump version to v202611-00062
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:27:14 +01:00
Richard Zink 22cc4d9446 Merge branch 'worktree-agent-ae760a17ac34a8093' 2026-11-06 10:25:26 +01:00
Richard ZinkandClaude Opus 5.5 fdce6c282b Give every achievement its own pixel-art badge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:20:41 +01:00
1263 changed files with 16470 additions and 454 deletions

No files matched your search

+8
View File
@@ -4,3 +4,11 @@ Cargo.lock.bak
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__/
art/generated/fx/*-raw.png
Generated
+945 -4
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -17,3 +17,17 @@ 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), sound output and gamepads
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rfd = { version = "0.17", default-features = false, features = ["xdg-portal"] }
cpal = "0.18"
gilrs = "0.11"
+2
View File
@@ -31,4 +31,6 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY www/ /usr/share/nginx/html/
COPY --from=build /out/ /usr/share/nginx/html/
COPY --from=standalone /out/standalone/ /usr/share/nginx/html/standalone/
# packed once for gzip_static (nginx.conf)
RUN cd /usr/share/nginx/html && gzip -k -9 dungeoncraft.wasm mq_js_bundle.js standalone/DungeonCraft.html
EXPOSE 80
+28
View File
@@ -37,6 +37,34 @@ cp ~/.cargo/registry/src/*/macroquad-*/js/mq_js_bundle.js www/
python3 -m http.server 8080 -d www # open http://localhost:8080
```
### Desktop build
The game also runs as a native program (Linux, Windows, macOS), without server and multiplayer:
```sh
cargo build --release
./target/release/dungeoncraft
```
On Linux the build needs the ALSA headers (`alsa-lib` / `libasound2-dev`) and libudev for gamepads
(`libudev-dev`); the sound goes through ALSA (PipeWire and PulseAudio take it). Gamepads are read directly on the
desktop (gilrs, hot-plug included); the browser build uses the browser's Gamepad API. The settings, the autosave, the campaign progress and the profile are kept as files in the
data folder: `~/.local/share/dungeoncraft/` (Linux, or `$XDG_DATA_HOME/dungeoncraft`), `%APPDATA%\DungeonCraft`
(Windows), `~/Library/Application Support/DungeonCraft` (macOS); `DUNGEONCRAFT_DATA` chooses another one.
*Save Game* and saved campaigns land in its `saves/` folder; *Load Game* opens the system file dialog there (on
Linux through the desktop portal), and dropping a file onto the window works as well.
Release packages are built by `python3 tools/build_desktop.py [linux] [windows] [macos]` into `dist/`:
- **Linux**: `DungeonCraft-<version>-x86_64.AppImage`, built against glibc 2.28 (distributions from about 2019 on).
- **Windows**: `DungeonCraft-<version>-windows-x64.exe`, a single file for Windows 10 and newer, cross-compiled on Linux.
- **macOS**: `DungeonCraft-<version>-macos.zip` with a universal `DungeonCraft.app` (Apple silicon and Intel); only on a
Mac. It is signed ad hoc, not notarised: on first start open it with right click -> *Open*.
Linux and Windows need [zig](https://ziglang.org/) (`mise install zig@0.15` works) and
`cargo install --locked cargo-zigbuild`, plus `rustup target add x86_64-pc-windows-gnu`; the AppImage tool is downloaded
once into `~/.cache/dungeoncraft/`.
### Testing the game logic
The tests run natively: a headless simulation of whole games, a savegame round trip, rendering of all music modules and the audio engine:
+1 -1
View File
@@ -1 +1 @@
v202611-00061
v202611-00080
+32
View File
@@ -0,0 +1,32 @@
# Effect flipbooks (high-res)
Generated on 2026-11-08 with `fal-ai/nano-banana-pro` (`aspect_ratio: 16:9`, `resolution: 2K`,
one image per effect; the first try with one row of 8 frames at `8:1` came back empty), the
prompt in `batch.py`: the shared text followed by the effect.
```text
An animation sprite sheet of exactly 8 frames in a grid of 4 columns and 2 rows (reading order), each frame in its own equal square cell, the effect centred in every cell and filling most of it, the frames showing the animation from start to end. Hand-painted pixel art for a dark-fantasy dungeon strategy game, crisp pixels, glowing. The background is completely flat pure black (#000000) everywhere - no frames, no grid lines, no text, no numbers. The effect:
```
- **explosion**: a fiery explosion: a small bright flash, growing into a ball of orange and yellow fire with dark red edges, breaking up into flames and embers, fading into thin dark smoke.
- **smoke**: a puff of grey smoke: a small dense cloud growing larger and softer, drifting apart and fading out.
- **burst**: a magic burst seen from the front: a bright white spark growing into a ring of white light rays and sparkles, then scattering into small fading sparkles (pure white and light grey, so it can be tinted).
- **impact**: a hit impact star: a sharp white flash with jagged rays, then smaller sparks flying outwards and fading (pure white and light grey, so it can be tinted).
- **heal**: a healing effect: soft green glowing crosses and sparkles rising upwards and fading, around a gentle green glow.
- **orbs**: eight different glowing magic projectiles, one per cell (not an animation): 1 a fireball with a flaming tail, 2 the same fireball flickering, 3 a white glowing orb with a soft halo (to be tinted), 4 the same white orb pulsing larger, 5 a holy golden light orb, 6 a dark violet shadow orb with wisps, 7 a glowing red rune orb, 8 a blue arcane orb with small sparkles.
## Processing
The explosion sheet had a grey border around its black panel; it was cut to the panel first
(`explosion-trim.png`). Then
```bash
python3 tools/make_fx.py fx.png explosion-trim.png smoke-raw.png burst-raw.png impact-raw.png heal-raw.png orbs-raw.png --margin 0.12
```
cuts the 2x4 frames, turns the dark background into transparency (alpha from the brightness, so
glows and smoke keep soft edges) and writes one row of eight 64x64 frames per effect;
`fx.png` is copied to `assets/textures/fx/fx.png`. In the game (`fx_sprite` in
`src/render3d.rs`, high-res only): fireballs and cannon blasts explode, other flashes burst in
their colour, smoke puffs, hits flash with an impact star, healing rises in green, and the
magic projectiles are glowing orbs (the fireball's tail points back along its way).
+13
View File
@@ -0,0 +1,13 @@
# Effect flipbooks of the high-res style (2026-11-08): one sheet per effect, 8 frames in a row.
STYLE = ("An animation sprite sheet of exactly 8 frames in a grid of 4 columns and 2 rows (reading order), each frame in its own equal square cell, the effect centred in every "
"cell and filling most of it, the frames showing the animation from start to end. Hand-painted pixel art for a dark-fantasy dungeon "
"strategy game, crisp pixels, glowing. The background is completely flat pure black (#000000) everywhere - no frames, no grid lines, "
"no text, no numbers. The effect: ")
FX = {
"explosion": "a fiery explosion: a small bright flash, growing into a ball of orange and yellow fire with dark red edges, breaking up into flames and embers, fading into thin dark smoke.",
"smoke": "a puff of grey smoke: a small dense cloud growing larger and softer, drifting apart and fading out.",
"burst": "a magic burst seen from the front: a bright white spark growing into a ring of white light rays and sparkles, then scattering into small fading sparkles (pure white and light grey, so it can be tinted).",
"impact": "a hit impact star: a sharp white flash with jagged rays, then smaller sparks flying outwards and fading (pure white and light grey, so it can be tinted).",
"heal": "a healing effect: soft green glowing crosses and sparkles rising upwards and fading, around a gentle green glow.",
"orbs": "eight different glowing magic projectiles, one per cell (not an animation): 1 a fireball with a flaming tail, 2 the same fireball flickering, 3 a white glowing orb with a soft halo (to be tinted), 4 the same white orb pulsing larger, 5 a holy golden light orb, 6 a dark violet shadow orb with wisps, 7 a glowing red rune orb, 8 a blue arcane orb with small sparkles.",
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+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
+309
View File
@@ -0,0 +1,309 @@
# 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):
```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 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:
```text
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) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

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