691 lines
29 KiB
Python
691 lines
29 KiB
Python
"""Prompt templates and composition logic for sprite generation via Stable Diffusion.
|
|
|
|
All data is module-level constants. All functions are pure — no side effects, no file I/O.
|
|
Target model: juggernaut-xi-v11 (SDXL).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Style prefixes by category
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# ==========================================================================
|
|
# BACKGROUNDS — fill the entire tile, no transparency, top-down textures
|
|
# ==========================================================================
|
|
_BG_STYLE = (
|
|
"top-down aerial photograph looking straight down at the ground, "
|
|
"natural terrain surface texture filling the entire frame edge to edge, "
|
|
"hand-painted fantasy art, rich colors, detailed, "
|
|
"NO horizon, NO sky, NO perspective, NO objects, NO characters, NO buildings, "
|
|
"seamless tileable ground texture, masterpiece, best quality"
|
|
)
|
|
|
|
# ==========================================================================
|
|
# LAYERS — transparent background, placed ON TOP of biome tiles
|
|
# ==========================================================================
|
|
_LAYER_COMMON = "simple background, clean crisp edges, masterpiece, best quality"
|
|
|
|
STYLE_PREFIXES: dict[str, str] = {
|
|
# --- BACKGROUNDS ---
|
|
"terrain": _BG_STYLE,
|
|
"biome_grid": _BG_STYLE,
|
|
"edges": _BG_STYLE + ", transition gradient between two terrain types",
|
|
|
|
# --- LAYERS: background removed by rembg in post-processing ---
|
|
"units": (
|
|
"single character game sprite, simple background, "
|
|
"isometric three-quarter rear view, character walking toward lower-left corner, "
|
|
"hand-painted digital fantasy art, Warcraft III style unit, "
|
|
"rich saturated colors, sharp clean edges, full body visible, masterpiece"
|
|
),
|
|
"buildings": (
|
|
"isometric game building sprite, "
|
|
"3/4 aerial view showing the roof and two walls, "
|
|
"single medieval fantasy building, "
|
|
"painted illustration like Age of Empires II or Stronghold building art, "
|
|
"detailed stone and timber construction, "
|
|
f"{_LAYER_COMMON}"
|
|
),
|
|
"resources": (
|
|
"simple background, "
|
|
"single small game resource icon, 3/4 isometric view, "
|
|
"one isolated glowing resource node, NOT terrain, NOT ground texture, "
|
|
"polished hand-painted game art like Warcraft III or Civilization V resource, "
|
|
"vibrant colors, sharp detail, clean crisp edges, masterpiece, best quality"
|
|
),
|
|
"improvements": (
|
|
"simple background, "
|
|
"tiny game map icon sprite, 3/4 isometric view from above, "
|
|
"ONE small simple object centered in frame, nothing else, "
|
|
"miniature game asset like a Civilization V map icon, "
|
|
"painted fantasy art, clean simple, "
|
|
"clean crisp edges, masterpiece, best quality"
|
|
),
|
|
"spells": (
|
|
"magical spell effect icon for a game spellbook, "
|
|
"abstract glowing magical energy centered in frame, "
|
|
"like a World of Warcraft or Diablo ability icon, "
|
|
"pure energy, particles, runes, or elemental force, "
|
|
"NO person, NO character, NO figure, NO face, "
|
|
"NO border, NO frame, NO ornament, NO medallion, NO circle frame, "
|
|
"clean composition, vivid magical colors, simple dark background, "
|
|
"masterpiece, best quality"
|
|
),
|
|
"ui": (
|
|
"clean flat game UI icon, "
|
|
"single bold symbol centered, "
|
|
"like a Civilization V yield icon or tech tree icon, "
|
|
f"{_LAYER_COMMON}"
|
|
),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Negative prompts by category
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Background negatives: ban objects, transparency, perspective
|
|
_BG_NEG = (
|
|
"text, watermark, blurry, low quality, anime, 3d render, photograph, "
|
|
"horizon, sky, clouds, sun, moon, perspective, vanishing point, "
|
|
"person, character, creature, building, structure, object, "
|
|
"transparent, alpha, border, frame, hexagon, geometric, abstract, UI"
|
|
)
|
|
|
|
# Layer negatives: ban scenery, backgrounds, terrain
|
|
_LAYER_NEG = (
|
|
"text, watermark, blurry, low quality, anime, photograph, "
|
|
"white background, black background, scenery background, "
|
|
"terrain, landscape, sky, horizon, ground texture, "
|
|
"multiple objects, collage, grid, busy scene, "
|
|
"border, frame, hexagon, geometric, abstract, UI"
|
|
)
|
|
|
|
NEGATIVES: dict[str, str] = {
|
|
"terrain": _BG_NEG,
|
|
"biome_grid": _BG_NEG,
|
|
"edges": _BG_NEG,
|
|
"units": _LAYER_NEG + ", multiple characters, crowd, group, turnaround, reference sheet, model sheet, concept art, multiple poses, multiple views, character sheet, realistic, photograph, photorealistic, 3d render, grey background, brown background, studio lighting, shadow on ground, facing camera, facing forward, front view, looking at viewer, anime, pixel art, chibi",
|
|
"buildings": _LAYER_NEG + ", front elevation, straight-on view, multiple buildings, city, street",
|
|
"resources": _LAYER_NEG + ", seamless texture, tileable pattern, inventory item, floating object, full-frame texture, raw meat, food",
|
|
"improvements": _LAYER_NEG + ", complex scene, multiple structures, city, village, town, farm scene, landscape, panorama, multiple buildings, people, farmers, vehicles, tractor",
|
|
"spells": (
|
|
"text, watermark, blurry, low quality, anime, photograph, "
|
|
"person, character, human, angel, figure, face, body, wings, creature, "
|
|
"terrain, landscape, building, scenery, "
|
|
"border, frame, hexagon, geometric, UI, "
|
|
"medallion, circle, ornament, marble, stone, white background"
|
|
),
|
|
"ui": _LAYER_NEG + ", realistic, detailed, complex",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dimension modifier prompts
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Building/structure race aesthetics
|
|
RACE_AESTHETICS: dict[str, str] = {
|
|
"high_elves": "elegant elven architecture, pointed crystalline spires, silver-blue and white palette, graceful organic curves, luminous materials",
|
|
"humans": "medieval human architecture, practical sturdy design, heraldic banners, warm stone and timber, copper and gold accents",
|
|
"dwarves": "dwarven stonework, massive carved rock, rune-inscribed walls, copper and bronze fittings, underground fortress aesthetic",
|
|
"orcs": "orcish construction, bone and leather decorations, tribal totems, dark iron spikes, rough-hewn wood and stone",
|
|
}
|
|
|
|
# Character race aesthetics (for units — describes the person, not buildings)
|
|
RACE_UNIT_AESTHETICS: dict[str, str] = {
|
|
"high_elves": "tall slender elf, pointed ears, pale skin, silver-blonde hair, elegant light armor, graceful",
|
|
"humans": "human, medium build, practical armor, heraldic tabard, weathered face",
|
|
"dwarves": "dwarf, short stocky muscular, heavy plate and chainmail, rune-etched armor, wide stance",
|
|
"orcs": "orc, tall muscular green skin, tusks, tribal war paint, bone and leather armor, fierce expression",
|
|
}
|
|
|
|
GENDER_MODIFIERS: dict[str, str] = {
|
|
"male": "male, masculine build",
|
|
"female": "female, feminine build",
|
|
}
|
|
|
|
# Race-specific gender overrides (e.g. female dwarves have no beards)
|
|
RACE_GENDER_OVERRIDES: dict[tuple[str, str], str] = {
|
|
("dwarves", "female"): "female dwarf, no beard, braided hair, sturdy feminine build, shorter and stocky",
|
|
("dwarves", "male"): "male dwarf, thick braided beard, burly masculine build, shorter and stocky",
|
|
}
|
|
|
|
QUALITY_MODIFIERS: dict[str, dict[int, str]] = {
|
|
"terrain": {
|
|
1: "sparse nascent, recently formed, thin patchy coverage, young ecosystem",
|
|
2: "developing, growing, partial coverage, maturing",
|
|
3: "established standard, typical healthy, mature ecosystem",
|
|
4: "flourishing, dense rich coverage, thriving biodiversity",
|
|
5: "ancient peak ecosystem, pristine old-growth, dense lush primordial",
|
|
},
|
|
"units": {
|
|
1: "raw recruit, basic equipment, worn leather, simple weapons",
|
|
2: "trained soldier, decent equipment, serviceable armor",
|
|
3: "veteran warrior, quality equipment, well-maintained gear, battle-scarred",
|
|
4: "elite champion, masterwork equipment, ornate armor, commanding presence",
|
|
5: "legendary hero, legendary equipment, radiant armor, aura of power",
|
|
},
|
|
"buildings": {
|
|
1: "crude construction, basic materials, partially built, scaffolding visible",
|
|
2: "functional construction, standard materials, well-maintained",
|
|
3: "well-built, quality stonework, mature, established, decorated",
|
|
4: "grand construction, fine materials, ornate details, impressive",
|
|
5: "magnificent masterwork, legendary craftsmanship, monumental, glowing with power",
|
|
},
|
|
"resources": {
|
|
1: "sparse small deposit, barely visible, traces",
|
|
2: "modest deposit, visible but limited",
|
|
3: "standard healthy deposit, clearly visible, moderate abundance",
|
|
4: "rich abundant deposit, impressive quantity, gleaming",
|
|
5: "legendary massive deposit, overwhelming abundance, radiant with power",
|
|
},
|
|
"spells": {
|
|
1: "faint weak magical effect, dim glow, minor energy",
|
|
2: "moderate magical effect, visible energy, growing power",
|
|
3: "strong magical effect, vivid energy, impressive display",
|
|
4: "powerful magical effect, intense radiance, devastating force",
|
|
5: "cataclysmic magical effect, blinding energy, reality-warping power, legendary",
|
|
},
|
|
"improvements": {
|
|
1: "crude basic construction, rough materials, newly placed",
|
|
2: "functional improvement, standard construction",
|
|
3: "well-developed, quality materials, efficient, established",
|
|
},
|
|
"units_civilian": {
|
|
1: "shabby peasant clothes, worn patched fabric, simple tools",
|
|
2: "practical traveler gear, leather satchel, walking staff, sturdy boots",
|
|
3: "well-equipped explorer, quality leather, maps and instruments",
|
|
},
|
|
}
|
|
|
|
SCHOOL_AESTHETICS: dict[str, str] = {
|
|
"life": "golden divine light, white marble, angelic motifs, holy symbols, pristine and sacred",
|
|
"death": "dark gothic stone, skulls and bones, purple-black energy, necromantic symbols, eerie green glow",
|
|
"chaos": "volcanic rock and fire, red-orange flames, demonic runes, molten metal, aggressive spiky architecture",
|
|
"nature": "living wood and vines, green leaves, druidic symbols, stone circles, natural organic forms",
|
|
"aether": "crystalline blue-white, floating arcane runes, ethereal glow, geometric precision, translucent materials",
|
|
}
|
|
|
|
# Energy-only school colors for spells (no material references like marble/stone/rock)
|
|
SCHOOL_ENERGY_COLORS: dict[str, str] = {
|
|
"life": "golden-white divine light, warm radiant glow",
|
|
"death": "purple-black dark energy, eerie green necromantic glow",
|
|
"chaos": "red-orange fire and flames, molten glowing energy",
|
|
"nature": "green living energy, verdant glow, leaf and vine motifs",
|
|
"aether": "blue-white crystalline energy, arcane geometric light",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Unit sub-templates by combat type
|
|
# Overrides the generic "units" STYLE_PREFIX when a combat_type matches
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Production formula — proven with juggernaut-xi-v11 at guidance 9.0
|
|
# Winner: "E_composition" from prompt lab testing
|
|
# Key: "simple background" + "isometric three-quarter rear view" +
|
|
# "walking toward lower-left" + "Warcraft III style"
|
|
# Background removal handled by rembg (U2Net) in post-processing.
|
|
|
|
_UNIT_CORE = (
|
|
"single character game sprite, simple background, "
|
|
"isometric three-quarter rear view, character walking toward lower-left corner, "
|
|
"{entity}, "
|
|
"hand-painted digital fantasy art, Warcraft III style unit, "
|
|
"rich saturated colors, sharp clean edges, full body visible, masterpiece"
|
|
)
|
|
|
|
def _unit_style(combat_extra: str = "") -> str:
|
|
"""Build unit style prefix. compose_prompt inserts entity content between parts."""
|
|
extra = f", {combat_extra}" if combat_extra else ""
|
|
return (
|
|
"single character game sprite, simple background, "
|
|
"isometric three-quarter rear view, character walking toward lower-left corner{extra}"
|
|
).replace("{extra}", extra)
|
|
|
|
UNIT_STYLE_BY_COMBAT_TYPE: dict[str, str] = {
|
|
"melee": _unit_style("armored warrior holding melee weapon"),
|
|
"ranged": _unit_style("ranged fighter holding weapon ready to fire"),
|
|
"cavalry": (
|
|
"single mounted unit game sprite, simple background, "
|
|
"REAR VIEW of rider on warhorse, horse walking AWAY from viewer toward lower-left, "
|
|
"rider's BACK facing camera, back of rider visible, horse hindquarters and tail visible, "
|
|
"NO front hooves visible, NO chest of rider visible, NO face of rider visible, "
|
|
"isometric three-quarter view from slightly above, full mount visible, "
|
|
"hand-painted digital fantasy art, Warcraft III style, "
|
|
"rich saturated colors, sharp clean edges, masterpiece"
|
|
),
|
|
"siege": (
|
|
"single siege engine game sprite, simple background, "
|
|
"isometric three-quarter view from above, angled toward lower-left, "
|
|
"hand-painted digital fantasy art, Warcraft III style, "
|
|
"heavy wood and iron war machine, no people, "
|
|
"rich saturated colors, sharp clean edges, masterpiece"
|
|
),
|
|
"flying": (
|
|
"single flying creature game sprite, simple background, "
|
|
"isometric view from above, soaring toward lower-left, wings spread, "
|
|
"hand-painted digital fantasy art, Warcraft III style, "
|
|
"rich saturated colors, sharp clean edges, masterpiece"
|
|
),
|
|
"marine": (
|
|
"single ship game sprite, simple background, "
|
|
"isometric three-quarter view from above, sailing toward lower-left, "
|
|
"hand-painted digital fantasy art, Civilization V style naval unit, "
|
|
"rich saturated colors, sharp clean edges, masterpiece"
|
|
),
|
|
"civilian": _unit_style("civilian carrying tools or supplies, traveler clothes"),
|
|
"specialist": _unit_style("support character with specialized equipment"),
|
|
}
|
|
|
|
|
|
def get_unit_style(combat_type: str) -> str:
|
|
"""Get the style prefix for a unit based on its combat type."""
|
|
return UNIT_STYLE_BY_COMBAT_TYPE.get(combat_type, STYLE_PREFIXES["units"])
|
|
|
|
|
|
COMBAT_TYPE_FLAVORS: dict[str, str] = {
|
|
"melee": "armored warrior, close combat stance, weapon drawn",
|
|
"ranged": "ranged fighter, bow or crossbow ready, quiver",
|
|
"cavalry": "mounted on warhorse moving away, horse's back and tail visible, rider's back to viewer",
|
|
"siege": "large war machine, siege engine, heavy wood and iron",
|
|
"flying": "winged creature in flight, soaring pose",
|
|
"specialist": "support character, magical or tactical equipment",
|
|
"civilian": "non-combat civilian, traveler clothes, pack and supplies",
|
|
"sea": "naval vessel, ship on water, sails and rigging",
|
|
"archon": "powerful magical caster, arcane energy swirling, ethereal presence",
|
|
}
|
|
|
|
KEYWORD_FLAVORS: dict[str, str] = {
|
|
"undead": "undead skeletal, decaying, dark necromantic energy",
|
|
"flying": "wings spread, soaring, aerial",
|
|
"fire_breath": "breathing fire, flames, volcanic",
|
|
"magic_immune": "runic armor, anti-magic wards, iron and mithril",
|
|
"holy_aura": "divine golden glow, holy radiance",
|
|
"invisible": "shadowy, semi-transparent, stealthy",
|
|
"poison": "venomous, toxic green dripping, noxious",
|
|
"regeneration": "healing glow, green restoration energy",
|
|
"first_strike": "lightning-fast, blur of motion, deadly speed",
|
|
"trample": "massive, ground-shaking, unstoppable force",
|
|
"life_drain": "dark tendrils of energy, soul-absorbing purple glow",
|
|
"wall_breaker": "massive battering force, siege-breaking power",
|
|
}
|
|
|
|
VARIANT_MODIFIERS: list[str] = [
|
|
"",
|
|
", dramatic lighting, high contrast",
|
|
", soft diffused light, painterly",
|
|
", vibrant saturated colors, sharp detail",
|
|
", warm golden-hour light",
|
|
", cool moonlit atmosphere",
|
|
", cinematic depth of field",
|
|
", bold silhouette, rim lighting",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Biome grid fragment arrays
|
|
# ---------------------------------------------------------------------------
|
|
|
|
BIOME_TEMP: dict[int, str] = {
|
|
0: "frozen arctic, perpetual ice, deep winter",
|
|
1: "frigid cold, frost and bare earth, harsh winter",
|
|
2: "cool temperate, mild seasons, gentle weather",
|
|
3: "warm subtropical, rich growth, long summers",
|
|
4: "scorching tropical, intense heat, blazing sun",
|
|
}
|
|
|
|
BIOME_MOIST: dict[int, str] = {
|
|
0: "parched bone-dry arid, no water, cracked earth",
|
|
1: "dry sparse moisture, dusty, occasional rain",
|
|
2: "moderate rainfall, balanced seasons",
|
|
3: "lush wet, frequent rain, damp air",
|
|
4: "saturated waterlogged, standing water, constant rain",
|
|
}
|
|
|
|
BIOME_ELEV: dict[str, str] = {
|
|
"lowland": "flat lowland plains, gentle terrain",
|
|
"highland": "elevated rocky highland plateau, exposed stone",
|
|
"alpine": "steep alpine mountain slope, thin air, windswept",
|
|
}
|
|
|
|
BIOME_VEGETATION: dict[tuple[int, int], str] = {
|
|
(4, 0): "golden sand dunes with shadow ridges, scattered rocks, heat shimmer",
|
|
(4, 1): "sandy terrain with scattered thorny scrub bushes and rocks",
|
|
(4, 2): "golden savanna with acacia trees and tall yellow grass patches",
|
|
(4, 3): "tropical canopy from above, round broad-leaf treetops, vines",
|
|
(4, 4): "dense tangled jungle canopy, layered green leaves, vine bridges",
|
|
(3, 0): "dry steppe with dead grass clumps and cracked brown earth",
|
|
(3, 1): "sparse grassland with patches of green and brown bare earth",
|
|
(3, 2): "lush green meadow with wildflower patches and a winding dirt path",
|
|
(3, 3): "deciduous forest canopy from above, round green treetops, dappled shadows",
|
|
(3, 4): "dark dense rainforest canopy, moss and fern visible between trees",
|
|
(2, 0): "windswept grey-brown rocky ground with dead stubble and gravel",
|
|
(2, 1): "grey-green scrubland with hardy low bushes on rocky terrain",
|
|
(2, 2): "cool green meadow with short grass and morning dew droplets",
|
|
(2, 3): "conifer forest from above, triangular dark-green pine treetops",
|
|
(2, 4): "murky bog with dark water, moss patches, dead tree stumps",
|
|
(1, 0): "frozen grey wasteland, cracked permafrost, scattered frost crystals",
|
|
(1, 1): "frozen tundra with pale lichen spots on grey-blue ground",
|
|
(1, 2): "frost-covered yellowed grass on frozen brown-grey earth",
|
|
(1, 3): "frozen wetland, ice-crusted brown reed stalks in dark water",
|
|
(1, 4): "frozen black marsh, thin cracked ice sheets over dark water",
|
|
(0, 0): "white snow drifts with subtle blue shadows, wind-carved ripples",
|
|
(0, 1): "flat snow plain with wind patterns and a few exposed rocks",
|
|
(0, 2): "snow field with frozen plant tips poking through white surface",
|
|
(0, 3): "blue-white pack ice with visible pressure ridges and cracks",
|
|
(0, 4): "deep blue glacial ice surface, ancient, compressed, massive",
|
|
}
|
|
|
|
BIOME_GROUND: dict[tuple[int, int], str] = {
|
|
(4, 0): "smooth tan sand",
|
|
(4, 1): "sandy brown soil",
|
|
(4, 2): "warm reddish-brown earth",
|
|
(4, 3): "dark rich soil under leaf litter",
|
|
(4, 4): "dark mud under shallow water",
|
|
(3, 0): "hard packed light brown dirt",
|
|
(3, 1): "dusty grey-brown soil",
|
|
(3, 2): "dark brown-green earth",
|
|
(3, 3): "brown leaf litter on dark soil",
|
|
(3, 4): "dark wet peat",
|
|
(2, 0): "grey gravel and frozen dirt",
|
|
(2, 1): "grey rocky soil",
|
|
(2, 2): "dark damp earth",
|
|
(2, 3): "brown needle-covered forest floor",
|
|
(2, 4): "waterlogged dark peat",
|
|
(1, 0): "grey frozen permafrost",
|
|
(1, 1): "blue-grey frozen ground",
|
|
(1, 2): "brown frozen soil",
|
|
(1, 3): "dark frozen mud",
|
|
(1, 4): "black frozen water",
|
|
(0, 0): "white compacted snow",
|
|
(0, 1): "white wind-packed snow",
|
|
(0, 2): "snow over frozen ground",
|
|
(0, 3): "smooth blue-white ice",
|
|
(0, 4): "deep blue ice",
|
|
}
|
|
|
|
BIOME_PALETTE: dict[int, str] = {
|
|
0: "MANDATORY COLOR: white and pale blue ONLY, ice crystal silver, frozen winter, NO warm colors NO brown NO green",
|
|
1: "MANDATORY COLOR: cold grey-blue and frost-white, muted desaturated, winter palette, NO warm tones",
|
|
2: "MANDATORY COLOR: cool greens and earth browns, balanced natural colors",
|
|
3: "MANDATORY COLOR: rich warm greens and golden-yellow, lush vibrant",
|
|
4: "MANDATORY COLOR: intense hot golden-yellow, burnt orange, tropical vivid, NO blue NO cool tones",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generation and target sizes
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_GENERATION_SIZES: dict[str, tuple[int, int]] = {
|
|
# Backgrounds: wide for hex crop
|
|
"terrain": (1024, 1024),
|
|
"biome_grid": (1024, 1024),
|
|
"edges": (1024, 1024),
|
|
# Layers: square at SDXL optimal resolution
|
|
"units": (1024, 1024),
|
|
"buildings": (1024, 1024),
|
|
"resources": (1024, 1024),
|
|
"improvements": (1024, 1024),
|
|
"spells": (1024, 1024),
|
|
"ui": (512, 512),
|
|
}
|
|
|
|
_TARGET_SIZES: dict[str, tuple[int, int]] = {
|
|
"terrain": (384, 332),
|
|
"biome_grid": (384, 332),
|
|
"edges": (384, 332),
|
|
"units": (256, 256),
|
|
"buildings": (128, 128),
|
|
"resources": (64, 64),
|
|
"improvements": (64, 64),
|
|
"spells": (128, 128),
|
|
"ui": (64, 64),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Default quality ranges by category
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_DEFAULT_QUALITY_RANGES: dict[str, tuple[int, int] | None] = {
|
|
"terrain": (1, 5),
|
|
"biome_grid": (1, 5),
|
|
"resources": (1, 5),
|
|
"spells": (1, 5),
|
|
"improvements": (1, 3),
|
|
"edges": None,
|
|
"ui": None,
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Functions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def compose_prompt(
|
|
category: str,
|
|
entity_data: dict,
|
|
dimensions: dict | None = None,
|
|
) -> str:
|
|
"""Build the full prompt for a sprite.
|
|
|
|
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] = []
|
|
|
|
# ── UNITS: three-part prompt (composition → entity → style tail) ──
|
|
if category == "units":
|
|
# PART 1: Composition setup (camera, direction)
|
|
combat_type = entity_data.get("combat_type", "")
|
|
parts.append(get_unit_style(combat_type))
|
|
|
|
# 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])
|
|
|
|
# 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", [])
|
|
for kw in keywords:
|
|
flavor = KEYWORD_FLAVORS.get(kw)
|
|
if flavor:
|
|
parts.append(flavor)
|
|
|
|
# PART 5: Style tail (AFTER entity content — avoids orc bias)
|
|
parts.append(
|
|
"hand-painted digital fantasy art, bold painterly RPG game style, "
|
|
"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:
|
|
parts.append(SCHOOL_ENERGY_COLORS[school])
|
|
elif school in SCHOOL_AESTHETICS:
|
|
parts.append(SCHOOL_AESTHETICS[school])
|
|
|
|
# Race aesthetic (buildings get architecture)
|
|
race = dims.get("race")
|
|
if race and race in RACE_AESTHETICS:
|
|
parts.append(RACE_AESTHETICS[race])
|
|
|
|
# Gender
|
|
gender = dims.get("gender")
|
|
if gender and gender in GENDER_MODIFIERS:
|
|
parts.append(GENDER_MODIFIERS[gender])
|
|
|
|
# Style prefix
|
|
prefix = STYLE_PREFIXES.get(category, "")
|
|
if prefix:
|
|
parts.append(prefix)
|
|
|
|
# Quality modifier
|
|
quality = dims.get("quality")
|
|
if quality is not None:
|
|
qual_category = category
|
|
if qual_category == "biome_grid":
|
|
qual_category = "terrain"
|
|
qual_table = QUALITY_MODIFIERS.get(qual_category)
|
|
if qual_table and quality in qual_table:
|
|
parts.append(qual_table[quality])
|
|
|
|
return ", ".join(parts)
|
|
|
|
|
|
def compose_biome_prompt(
|
|
temp: int,
|
|
moist: int,
|
|
elev: str,
|
|
quality: int,
|
|
) -> str | None:
|
|
"""Build prompt for a biome grid cell.
|
|
|
|
Returns None if the combination is unrealistic (filtered by is_valid_biome).
|
|
"""
|
|
if not is_valid_biome(temp, moist, elev):
|
|
return None
|
|
|
|
parts: list[str] = []
|
|
|
|
# Style prefix
|
|
parts.append(STYLE_PREFIXES["biome_grid"])
|
|
|
|
# Vegetation
|
|
veg = BIOME_VEGETATION.get((temp, moist))
|
|
if veg:
|
|
parts.append(veg)
|
|
|
|
# Ground material
|
|
ground = BIOME_GROUND.get((temp, moist))
|
|
if ground:
|
|
parts.append(ground)
|
|
|
|
# Elevation
|
|
elev_desc = BIOME_ELEV.get(elev)
|
|
if elev_desc:
|
|
parts.append(elev_desc)
|
|
|
|
# Quality (terrain table)
|
|
qual_table = QUALITY_MODIFIERS.get("terrain")
|
|
if qual_table and quality in qual_table:
|
|
parts.append(qual_table[quality])
|
|
|
|
# Palette (mandatory color directive)
|
|
palette = BIOME_PALETTE.get(temp)
|
|
if palette:
|
|
parts.append(palette)
|
|
|
|
return ", ".join(parts)
|
|
|
|
|
|
def get_negative(category: str) -> str:
|
|
"""Get the negative prompt for a category."""
|
|
return NEGATIVES.get(category, "")
|
|
|
|
|
|
def get_variant_modifier(variant_index: int) -> str:
|
|
"""Get the style modifier for variant N (cycles through VARIANT_MODIFIERS)."""
|
|
return VARIANT_MODIFIERS[variant_index % len(VARIANT_MODIFIERS)]
|
|
|
|
|
|
def is_valid_biome(temp: int, moist: int, elev: str) -> bool:
|
|
"""Check if a biome combination is realistic.
|
|
|
|
Filter rules:
|
|
- No frozen(0) + saturated(4) + alpine
|
|
- No scorching(4) + alpine
|
|
"""
|
|
if temp == 0 and moist == 4 and elev == "alpine":
|
|
return False
|
|
if temp == 4 and elev == "alpine":
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_quality_range(
|
|
category: str,
|
|
entity_data: dict | None = None,
|
|
) -> tuple[int, int] | None:
|
|
"""Get the quality range for an entity.
|
|
|
|
If entity_data has a 'quality_range' field, use that instead of defaults.
|
|
"""
|
|
if entity_data and "quality_range" in entity_data:
|
|
qr = entity_data["quality_range"]
|
|
return (qr[0], qr[1])
|
|
|
|
# Categories with fixed defaults
|
|
if category in _DEFAULT_QUALITY_RANGES:
|
|
return _DEFAULT_QUALITY_RANGES[category]
|
|
|
|
# Units — range depends on type
|
|
if category == "units":
|
|
if entity_data is None:
|
|
return (1, 3)
|
|
school = entity_data.get("school")
|
|
entity_type = entity_data.get("type", "")
|
|
if entity_type == "wild" or entity_data.get("is_wild"):
|
|
return (1, 3)
|
|
if school and school not in ("mundane", ""):
|
|
return (2, 5)
|
|
return (1, 3)
|
|
|
|
# Buildings — range depends on tier/type
|
|
if category == "buildings":
|
|
if entity_data is None:
|
|
return (1, 3)
|
|
entity_type = entity_data.get("type", "")
|
|
if entity_type in ("advanced", "wonder"):
|
|
return (3, 5)
|
|
return (1, 3)
|
|
|
|
return None
|
|
|
|
|
|
def get_generation_size(category: str) -> tuple[int, int]:
|
|
"""Get the generation resolution (width, height) for a category."""
|
|
return _GENERATION_SIZES.get(category, (512, 512))
|
|
|
|
|
|
def get_target_size(category: str) -> tuple[int, int]:
|
|
"""Get the final sprite size (width, height) for a category."""
|
|
return _TARGET_SIZES.get(category, (64, 64))
|