feat(sprite-generation): Update sprite ranking algorithm and optimize database schema for better performance

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-26 14:35:37 -07:00
parent be808cf659
commit 1268090d5d
5 changed files with 70 additions and 49 deletions

View file

@ -236,14 +236,14 @@ _UNIT_CORE = (
)
def _unit_style(combat_extra: str = "") -> str:
"""Build unit style prefix. Entity placeholder {entity} filled by compose_prompt."""
"""Build unit style prefix. compose_prompt inserts entity content between parts."""
extra = f", {combat_extra}" if combat_extra else ""
# PART 1: composition + direction (comes first)
# PART 2: entity content inserted by compose_prompt
# PART 3: style (comes after entity so SDXL weights entity higher)
return (
"single character game sprite on solid lime green (#00ff00) background, "
"isometric three-quarter rear view, character walking toward lower-left corner{extra}, "
"hand-painted digital fantasy art, Warcraft III style unit, "
"non-green armor and clothing, metal and leather colors, "
"rich saturated colors, sharp clean edges, full body visible, masterpiece"
"isometric three-quarter rear view, character walking toward lower-left corner{extra}"
).replace("{extra}", extra)
UNIT_STYLE_BY_COMBAT_TYPE: dict[str, str] = {
@ -470,32 +470,40 @@ def compose_prompt(
) -> str:
"""Build the full prompt for a sprite.
SDXL weights early tokens most heavily. For units, the style prefix
(green bg, facing direction, camera angle, art style) must come FIRST
because those are the hardest dimensions to get right.
For units, order is: COMPOSITION RACE/GENDER/ENTITY STYLE TAIL
SDXL weights early tokens heaviest, so race/gender/entity must come
before style references to avoid Warcraft orc bias.
"""
dims = dimensions or {}
parts: list[str] = []
# For units: STYLE PREFIX FIRST (green bg + direction + camera + art style)
# These are the weakest scoring dimensions and need maximum SDXL weight.
# ── UNITS: three-part prompt (composition → entity → style tail) ──
if category == "units":
# PART 1: Composition setup (green bg, camera, direction)
combat_type = entity_data.get("combat_type", "")
prefix = get_unit_style(combat_type)
if prefix:
parts.append(prefix)
parts.append(get_unit_style(combat_type))
# Subject — what the image depicts (name + visual description)
name = entity_data.get("name", "")
description = entity_data.get("description", "")
if name:
parts.append(name)
if description:
parts.append(description[:120])
# PART 2: Race + gender FIRST (highest priority entity tokens)
race = dims.get("race")
gender = dims.get("gender")
if gender:
race_gender_key = (race, gender) if race else None
if race_gender_key and race_gender_key in RACE_GENDER_OVERRIDES:
parts.append(RACE_GENDER_OVERRIDES[race_gender_key])
elif gender in GENDER_MODIFIERS:
parts.append(GENDER_MODIFIERS[gender])
if race and race in RACE_UNIT_AESTHETICS:
parts.append(RACE_UNIT_AESTHETICS[race])
# 2 — category-specific attributes
if category == "units":
combat_type = entity_data.get("combat_type", "")
# PART 3: Entity name + description
name = entity_data.get("name", "")
description = entity_data.get("description", "")
if name:
parts.append(name)
if description:
parts.append(description[:120])
# PART 4: Combat flavor + keywords
if combat_type and combat_type in COMBAT_TYPE_FLAVORS:
parts.append(COMBAT_TYPE_FLAVORS[combat_type])
keywords: list[str] = entity_data.get("keywords", [])
@ -504,7 +512,33 @@ def compose_prompt(
if flavor:
parts.append(flavor)
# school aesthetic (spells get energy-only colors, everything else gets full aesthetic)
# PART 5: Style tail (AFTER entity content — avoids orc bias)
parts.append(
"hand-painted digital fantasy art, bold painterly RPG game style, "
"non-green armor and clothing, metal and leather colors, "
"rich saturated colors, sharp clean edges, full body visible, masterpiece"
)
# Quality modifier
quality = dims.get("quality")
if quality is not None:
qual_category = "units_civilian" if combat_type == "civilian" else "units"
qual_table = QUALITY_MODIFIERS.get(qual_category)
if qual_table and quality in qual_table:
parts.append(qual_table[quality])
return ", ".join(parts)
# ── NON-UNITS: original flow ──
# Subject first
name = entity_data.get("name", "")
description = entity_data.get("description", "")
if name:
parts.append(name)
if description:
parts.append(description[:120])
# School aesthetic
school = entity_data.get("school") or dims.get("school")
if school and school not in ("mundane", ""):
if category == "spells" and school in SCHOOL_ENERGY_COLORS:
@ -512,40 +546,27 @@ def compose_prompt(
elif school in SCHOOL_AESTHETICS:
parts.append(SCHOOL_AESTHETICS[school])
# 4 — race aesthetic (units get character descriptions, buildings get architecture)
# Race aesthetic (buildings get architecture)
race = dims.get("race")
if race:
if category == "units" and race in RACE_UNIT_AESTHETICS:
parts.append(RACE_UNIT_AESTHETICS[race])
elif race in RACE_AESTHETICS:
parts.append(RACE_AESTHETICS[race])
if race and race in RACE_AESTHETICS:
parts.append(RACE_AESTHETICS[race])
# gender modifier (with race-specific overrides)
# Gender
gender = dims.get("gender")
if gender:
race_gender_key = (race, gender) if race else None
if race_gender_key and race_gender_key in RACE_GENDER_OVERRIDES:
parts.append(RACE_GENDER_OVERRIDES[race_gender_key])
elif gender in GENDER_MODIFIERS:
parts.append(GENDER_MODIFIERS[gender])
if gender and gender in GENDER_MODIFIERS:
parts.append(GENDER_MODIFIERS[gender])
# 3 — STYLE PREFIX (non-units only — units already got theirs at position 0)
if category != "units":
prefix = STYLE_PREFIXES.get(category, "")
if prefix:
parts.append(prefix)
# Style prefix
prefix = STYLE_PREFIXES.get(category, "")
if prefix:
parts.append(prefix)
# 4 — quality modifier
# Quality modifier
quality = dims.get("quality")
if quality is not None:
qual_category = category
if qual_category == "biome_grid":
qual_category = "terrain"
# Civilian units get their own quality table (not soldier gear)
if category == "units":
combat_type = entity_data.get("combat_type", "")
if combat_type == "civilian":
qual_category = "units_civilian"
qual_table = QUALITY_MODIFIERS.get(qual_category)
if qual_table and quality in qual_table:
parts.append(qual_table[quality])

View file

@ -23,7 +23,7 @@ from engine.registry import SpriteRegistry
logger = logging.getLogger(__name__)
CONFIDENCE_THRESHOLD = 0.7
CONFIDENCE_THRESHOLD = 0.55
MIN_GOOD_VARIANTS = 3
# Per-category threshold overrides — small icons (64x64 target) need less fidelity

Binary file not shown.

Binary file not shown.

Binary file not shown.