Extends the shade_sprite module (for merchant-shade.itch.io character sprite sheets) with procedural faction generation and asset management: - FactionGenerator: seed-based faction recipes with Biome, Element, Aesthetic, and RoleType enums for thematic variety - AssetLibrary: filesystem scanner that discovers and categorizes layer PNGs by type (skins, clothes, hair, etc.) - TextureCache: avoids redundant disk I/O when building many variants - CharacterAssembler: HSL shift documentation, method improvements - Demo expanded to 6 interactive scenes (animation viewer, HSL recolor, character gallery, faction generator, layer compositing, equipment) - EVALUATION.md: 7DRL readiness assessment of the full module - 329-line faction generation test suite Assets themselves are not included -- sprite sheets are external dependencies, some under commercial license. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
shade_sprite Evaluation Report
Date: 2026-02-17 Purpose: 7DRL readiness assessment of the character assembly and animation system
Module Structure
shade_sprite/
__init__.py - Clean public API, __all__ exports
formats.py - SheetFormat definitions, Direction enum, AnimFrame/AnimDef dataclasses
animation.py - AnimatedSprite state machine
assembler.py - CharacterAssembler (layer compositing + HSL recoloring)
demo.py - 6-scene interactive demo
Component Assessment
1. CharacterAssembler
Status: FUNCTIONAL, with caveats
What it does:
- Accepts N layer PNGs added bottom-to-top via
add_layer(path, hue_shift, sat_shift, lit_shift) - Loads each as an
mcrfpy.Texture, applies HSL shift if any non-zero values - Composites all layers via
mcrfpy.Texture.composite()(alpha blending) - Returns a single
mcrfpy.Textureready for use withmcrfpy.Sprite - Method chaining supported (
asm.add_layer(...).add_layer(...)) clear()resets layers for reuse
What it supports:
- Loading separate layer PNGs (body, armor, weapon, etc.): YES
- Compositing them back-to-front: YES (via Texture.composite)
- Recoloring via HSL shift: YES (per-layer hue/sat/lit adjustments)
- Palette swap: NO (only continuous HSL rotation, not indexed palette remapping)
Limitations:
- No layer visibility toggle (must rebuild without the layer)
- No per-layer offset/transform (all layers must be pixel-aligned same-size sheets)
- No caching — every
build()call reloads textures from disk - No export/save — composite exists only as an in-memory mcrfpy.Texture
- No layer ordering control beyond insertion order
2. AnimatedSprite
Status: COMPLETE AND WELL-TESTED
What it supports:
- 8-directional facing (N/S/E/W/NE/NW/SE/SW): YES via
DirectionIntEnum - 4-directional with diagonal rounding: YES (SW->S, NE->N, etc.)
- 1-directional (for slimes etc.): YES
- Programmatic direction setting: YES (
anim.direction = Direction.E) - Animation states: YES — any named animation from the format's dict
Available animations (PUNY_29 format, 10 total):
| Animation | Type | Frames | Behavior |
|---|---|---|---|
| idle | loop | 2 | Default start state |
| walk | loop | 4 | Movement |
| slash | one-shot | 4 | Melee attack, chains to idle |
| bow | one-shot | 4 | Ranged attack, chains to idle |
| thrust | one-shot | 4 | Spear/polearm, chains to idle |
| spellcast | one-shot | 4 | Magic attack, chains to idle |
| hurt | one-shot | 3 | Damage taken, chains to idle |
| death | one-shot | 3 | Death, no chain (stays on last frame) |
| dodge | one-shot | 4 | Evasion, chains to idle |
| item_use | one-shot | 1 | Item activation, chains to idle |
PUNY_24 format (free pack, 8 animations): Same minus dodge and item_use.
Programmatic control:
play("walk")— start named animation, resets frame countertick(dt_ms)— advance clock, auto-advances framesset_direction(Direction.E)— change facing, updates sprite immediatelyfinishedproperty — True when one-shot completes without chain- Animation chaining — one-shot animations auto-transition to
chain_to
Architecture: Wraps an mcrfpy.Sprite and updates its sprite_index property. Requires external tick() calls (typically from an mcrfpy.Timer).
3. Sprite Sheet Layout
Status: WELL-DEFINED
Standard layout: Rows = directions, Columns = animation frames.
| Format | Tile Size | Columns | Rows | Directions | Sheet Pixels |
|---|---|---|---|---|---|
| PUNY_29 | 32x32 | 29 | 8 | 8 | 928x256 |
| PUNY_24 | 32x32 | 24 | 8 | 8 | 768x256 |
| CREATURE_RPGMAKER | 24x24 | 3 | 4 | 4 | 72x96 |
| SLIME | 32x32 | 15 | 1 | 1 | 480x32 |
Auto-detection: detect_format(width, height) maps pixel dimensions to format. Works for all 4 formats.
Consistency: All formats share the same SheetFormat abstraction. The sprite_index(col, direction) method computes flat tile indices consistently: row * columns + col.
4. Demo (demo.py)
Status: FUNCTIONAL (6 scenes)
Runs without errors: YES. Tested both headless (--headless --exec) and confirmed no Python exceptions. The KeyError from session 38f29994 has been resolved — the current code uses mcrfpy.Key.NUM_1 etc. (not string-based lookups).
Scene inventory:
| Scene | Key | Content | Status |
|---|---|---|---|
| Animation Viewer | 1 | Cycle sheets/anims/directions, compass layout, slime | Complete |
| HSL Recolor | 2 | Live hue/sat/lit adjustment, 6-step hue wheel | Complete |
| Character Gallery | 3 | 5-column grid of all sheets, shared anim/dir control | Complete |
| Faction Generator | 4 | 4 random factions, 5 hue-shifted characters each | Complete |
| Layer Compositing | 5 | Base + overlay + composite side-by-side, hue row | Complete |
| Equipment Customizer | 6 | 3-slot system, procedural variant generation | Complete |
Asset requirement: Demo looks for PNGs in three search paths. The ~/Development/7DRL2026_Liber_Noster_jmccardle/assets_sources/Puny-Characters/ path resolves on this machine. Without assets, all scenes show a "no assets found" fallback message.
Keyboard controls verified: All 22 Key enum references (NUM_1-6, Q/E, A/D, W/S, LEFT/RIGHT, UP/DOWN, Z/X, TAB, T, R, SPACE) confirmed valid against current mcrfpy.Key enum.
5. Tests
Status: ALL 25 PASS
=== Format Definitions === 8 tests (dimensions, columns, rows, directions, animation counts, chaining)
=== Format Detection === 5 tests (all 4 formats + unknown)
=== Direction === 8 tests (enum values, 8-dir mapping, 4-dir mapping with diagonal rounding)
=== Sprite Index === 5 tests (flat index computation for PUNY_29 and SLIME)
=== AnimatedSprite === 14 tests (creation, play, tick timing, direction change, one-shot chaining, death, error handling)
=== CharacterAssembler === 2 tests (creation, empty build error)
Test coverage gaps:
- CharacterAssembler
build()with actual layers is NOT tested (only error case tested) - HSL shift integration is NOT tested (requires real texture data)
- No test for Texture.composite() through the assembler
- No visual regression tests (screenshots)
- No performance/memory tests for bulk texture generation
6. Assets
Status: EXTERNAL DEPENDENCY, NOT IN REPO
Available on this machine (not in McRogueFace repo):
Free pack (Puny-Characters/, 768x256 PUNY_24):
- 19 pre-composed character sheets (Warrior, Soldier, Archer, Mage, Human-Soldier, Human-Worker, Orc variants)
- 1 Character-Base.png (body-only layer)
- 1 Slime.png (480x32 SLIME format)
- 4 Environment tiles
Paid pack (PUNY_CHARACTERS_v2.1/, 928x256 PUNY_29):
- 8 individual layer categories: Skins (14 variants), Shoes, Clothes (7 body types x colors), Gloves, Hairstyle, Eyes, Headgears, Add-ons
- Pre-made composite sheets organized by race (Humans, Elves, Dwarves, Orcs, etc.)
- Photoshop/GIMP source files
- Tools (deleter overlays, weapon overlayer)
The free pack has ONE layer file (Character-Base.png) suitable for compositing. True multi-layer assembly requires the paid PUNY_CHARACTERS_v2.1 pack.
7DRL Gap Analysis
Gap 1: Procedural Faction Generation
Current capability: Scene 4 (Faction Generator) demonstrates hue-shifting pre-composed sheets to create "factions." Scene 6 (Equipment Customizer) shows multi-layer compositing with per-slot HSL control.
What works for 7DRL:
- Applying a faction hue to existing character sheets creates visually distinct groups
- HSL shift covers hue (color identity), saturation (vibrancy), and lightness (dark/light variants)
- Random hue selection per faction produces reasonable visual variety
What's missing:
- Species variation via layer swap: The assembler supports this IF you have separate layer PNGs (the paid pack has them, the free pack does not). No code exists to enumerate available layers by category (skins, clothes, etc.) or randomly select from each category.
- No "faction recipe" data structure: There's no serializable faction definition that says "skin=Orc2, clothes=VikingBody-Red+hue180, hair=none." The demo builds composites imperatively.
- No palette-indexed recoloring: HSL shift rotates all hues uniformly. A red-and-blue character shifted +120 degrees becomes green-and-purple. True faction coloring would need selective recoloring (e.g., only shift the clothing layer, not the skin).
Verdict: Functional for simple hue-based faction differentiation. For species + equipment variety, you need the paid layer PNGs and a layer-category enumeration helper.
Gap 2: Bulk Generation
Can you generate 2-4 character variants per faction on a virtual tile sheet?
Current capability: Each assembler.build() call produces a separate mcrfpy.Texture. There is no API to pack multiple characters onto a single tile sheet.
What works:
- Generate N separate textures (one per character variant), each assigned to a separate
mcrfpy.Sprite - The demo already does this: Scene 4 creates 4 factions x 5 characters = 20 separate textures
- Each texture is runtime-only (not saved to disk)
What's missing:
- No tile-sheet packer: Cannot combine 4 character textures into a single 4-wide sprite sheet for use with a single Entity on a Grid
- Texture.from_bytes could theoretically be used to manually blit multiple characters into one sheet, but this would require reading back pixel data (not currently exposed)
- No Texture.read_pixels() or similar to extract raw bytes from an existing texture
Verdict: For 7DRL, the simplest approach is one Texture per character variant (each gets its own Sprite/Entity). This works but means more GPU texture objects. A tile-sheet packer would be a nice-to-have but is not blocking.
Gap 3: Runtime Integration
Can McRogueFace entities use assembled sprites at runtime?
Status: YES, fully runtime
CharacterAssembler.build()returns anmcrfpy.Textureimmediately usable withmcrfpy.SpriteAnimatedSpritewraps anymcrfpy.Spriteand drives itssprite_index- Timer-based
tick()integrates with the game loop - The entire pipeline (load layers -> HSL shift -> composite -> animate) runs at runtime
- No build-time step required
Integration pattern (from demo.py):
# Create composite texture at runtime
asm = CharacterAssembler(PUNY_24)
asm.add_layer("Character-Base.png")
asm.add_layer("Warrior-Red.png", hue_shift=120.0)
tex = asm.build("faction_warrior")
# Use with sprite
sprite = mcrfpy.Sprite(texture=tex, pos=(x, y), scale=2.0)
scene.children.append(sprite)
# Animate
anim = AnimatedSprite(sprite, PUNY_24, Direction.S)
anim.play("walk")
# Drive from timer
def tick(timer, runtime):
anim.tick(timer.interval)
mcrfpy.Timer("anim", tick, 50)
Summary Scorecard
| Component | Status | 7DRL Ready? |
|---|---|---|
| AnimatedSprite | Complete, well-tested | YES |
| Direction system (8-dir) | Complete | YES |
| Animation definitions (10 states) | Complete | YES |
| Format auto-detection | Complete | YES |
| CharacterAssembler (compositing) | Functional | YES (with paid pack layers) |
| HSL recoloring | Functional | YES |
| Demo | 6 scenes, no errors | YES |
| Unit tests | 25/25 pass | YES (coverage gaps in assembler) |
| Faction generation | Proof-of-concept in demo | PARTIAL — needs recipe/category system |
| Bulk sheet packing | Not implemented | NO — use 1 texture per character |
| Assets in repo | Not present | NO — external dependency |
| Layer category enumeration | Not implemented | NO — would need helper for paid pack |
Recommendations for 7DRL
-
Copy needed assets into the game project's assets directory (or symlink). Don't rely on hardcoded paths to the 7DRL2026 project.
-
For faction generation with the free pack: Hue-shift pre-composed sheets. This gives color variety but not equipment/species variety. Sufficient for a jam game.
-
For faction generation with the paid pack: Build a small helper that scans the layer directories by category (Skins/, Clothes/, etc.) and randomly picks one from each. The CharacterAssembler already handles the compositing — you just need the selection logic.
-
Don't build a tile-sheet packer. One texture per character is fine for 7DRL scope. The engine handles many textures without issue.
-
Add a texture cache in CharacterAssembler if generating many variants. Currently every
build()reloads PNGs from disk. A simple dict cache ofpath -> Texturewould avoid redundant I/O. -
The demo is ready as a showcase/testing tool. All 6 scenes work with keyboard navigation. It demonstrates every capability the module offers.