585 lines
24 KiB
Python
585 lines
24 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-xl-v9 (SDXL).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Style prefixes by category
|
|
# ---------------------------------------------------------------------------
|
|
|
|
STYLE_PREFIXES: dict[str, str] = {
|
|
"terrain": (
|
|
"top-down bird's-eye view looking straight down at the ground, "
|
|
"seamless tileable terrain texture for a hex-grid strategy game, "
|
|
"hand-painted fantasy art style like Civilization V or Heroes of Might and Magic, "
|
|
"NO perspective, NO horizon, NO sky, NO buildings, NO characters, "
|
|
"flat overhead satellite view of natural ground surface, "
|
|
"masterpiece, best quality, game asset"
|
|
),
|
|
"biome_grid": (
|
|
"top-down bird's-eye view looking straight down at the ground, "
|
|
"seamless tileable terrain texture for a hex-grid strategy game, "
|
|
"hand-painted fantasy art style like Civilization V or Heroes of Might and Magic, "
|
|
"NO perspective, NO horizon, NO sky, NO buildings, NO characters, "
|
|
"flat overhead satellite view of natural ground surface, "
|
|
"masterpiece, best quality, game asset"
|
|
),
|
|
"units": (
|
|
"isometric game character sprite on transparent background, "
|
|
"camera looking down from above at 60 degree angle, "
|
|
"you see the TOP OF THE HEAD and shoulders from above, "
|
|
"character appears short and foreshortened from the high camera, "
|
|
"like an Age of Empires II unit or Diablo II character sprite, "
|
|
"single small figure, full body visible, painted digital art, "
|
|
"clean edges ready to layer over terrain, "
|
|
"NO front-facing portrait, NO eye contact with camera, "
|
|
"transparent PNG background, masterpiece, best quality"
|
|
),
|
|
"buildings": (
|
|
"isometric building on transparent background, "
|
|
"camera looking down from above at 45-degree angle, "
|
|
"you can see the ROOF and TWO WALLS of the building, "
|
|
"NOT front-facing, NOT a facade, camera is elevated looking down, "
|
|
"single small building, painted digital art, "
|
|
"like Age of Empires II or Civilization V building, "
|
|
"clean edges ready to layer over terrain, "
|
|
"transparent PNG background, masterpiece, best quality"
|
|
),
|
|
"resources": (
|
|
"top-down overhead view of a natural resource deposit on the ground, "
|
|
"terrain overlay sprite for a hex-grid strategy game, "
|
|
"the resource is visible as a natural feature embedded in the earth, "
|
|
"like Civilization V resource icons visible on hex tiles when revealed, "
|
|
"painted fantasy game art, transparent background around the deposit, "
|
|
"small concentrated feature, NOT a full terrain texture, "
|
|
"masterpiece, best quality, game map overlay sprite"
|
|
),
|
|
"improvements": (
|
|
"single tile improvement on transparent background, "
|
|
"isometric view from above, small area of cultivated or developed land, "
|
|
"ONE simple feature like a small farm field or mine entrance, "
|
|
"like a Civilization V tile improvement overlaid on terrain, "
|
|
"painted fantasy game art, clean simple composition, "
|
|
"NOT a complex scene with multiple buildings, just ONE improvement, "
|
|
"transparent PNG background, masterpiece, best quality"
|
|
),
|
|
"spells": (
|
|
"magical spell effect icon on dark background, "
|
|
"abstract magical energy as the subject NOT a character, "
|
|
"glowing energy, particles, runes, or elemental force, "
|
|
"like a spell icon from Diablo or World of Warcraft ability bar, "
|
|
"circular or square icon composition, centered, "
|
|
"NO person, NO character, NO angel, just pure magical energy, "
|
|
"vivid colors on black background, masterpiece, best quality"
|
|
),
|
|
"edges": (
|
|
"seamless terrain edge transition, top-down bird's-eye view, "
|
|
"gradient blend between two terrain types for hex-grid tile edges, "
|
|
"painted fantasy art style, NO perspective, NO horizon, "
|
|
"masterpiece, best quality, game asset"
|
|
),
|
|
"ui": (
|
|
"clean fantasy game UI icon, simple flat design with subtle depth, "
|
|
"single symbolic icon centered on transparent background, "
|
|
"bold readable silhouette, like a Civilization V yield or tech icon, "
|
|
"masterpiece, best quality, game UI element"
|
|
),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Negative prompts by category
|
|
# ---------------------------------------------------------------------------
|
|
|
|
NEGATIVES: dict[str, str] = {
|
|
"terrain": (
|
|
"text, watermark, blurry, low quality, anime, 3d render, photo, "
|
|
"perspective view, horizon, sky, clouds, sun, moon, "
|
|
"person, character, creature, animal, building, structure, house, "
|
|
"hexagon pattern, geometric shape, abstract, honeycomb, border, frame, UI"
|
|
),
|
|
"biome_grid": (
|
|
"text, watermark, blurry, low quality, anime, 3d render, photo, "
|
|
"perspective view, horizon, sky, clouds, sun, moon, "
|
|
"person, character, creature, animal, building, structure, house, "
|
|
"hexagon pattern, geometric shape, abstract, honeycomb, border, frame, UI"
|
|
),
|
|
"units": (
|
|
"text, watermark, blurry, low quality, anime, photo, "
|
|
"white background, solid background, background scenery, terrain, landscape, sky, horizon, "
|
|
"front-facing, looking at camera, portrait, headshot, "
|
|
"multiple characters, crowd, group, army, "
|
|
"hexagon, geometric, border, frame, UI, card frame"
|
|
),
|
|
"buildings": (
|
|
"text, watermark, blurry, low quality, anime, photo, "
|
|
"white background, solid background, "
|
|
"front elevation, straight-on view, architectural drawing, blueprint, "
|
|
"terrain, landscape, sky, horizon, person, character, "
|
|
"multiple buildings, city, town, village, street, "
|
|
"hexagon, geometric, abstract, border, frame, UI"
|
|
),
|
|
"resources": (
|
|
"text, watermark, blurry, low quality, anime, photo, "
|
|
"dark background, black background, solid background, border, frame, "
|
|
"texture, pattern, seamless, tileable, "
|
|
"person, character, building, landscape, horizon, sky, "
|
|
"multiple objects, collage, grid, collection, "
|
|
"hexagon, geometric, abstract, UI"
|
|
),
|
|
"improvements": (
|
|
"text, watermark, blurry, low quality, anime, photo, "
|
|
"white background, "
|
|
"perspective view, horizon, sky, person, character, "
|
|
"multiple buildings, city, village, complex scene, "
|
|
"hexagon, geometric, abstract, border, frame, UI"
|
|
),
|
|
"spells": (
|
|
"text, watermark, blurry, low quality, anime, photo, "
|
|
"person, character, human, angel, figure, face, body, wings, "
|
|
"terrain, landscape, building, scenery, "
|
|
"hexagon, geometric, border, frame, UI, card frame"
|
|
),
|
|
"edges": (
|
|
"text, watermark, blurry, low quality, anime, 3d render, photo, "
|
|
"perspective view, horizon, sky, person, building, creature, "
|
|
"hexagon pattern, geometric shape, abstract, border, frame, UI"
|
|
),
|
|
"ui": (
|
|
"text, watermark, blurry, low quality, 3d render, photo, anime, "
|
|
"landscape, terrain, character, building, realistic, "
|
|
"border, frame, busy background"
|
|
),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dimension modifier prompts
|
|
# ---------------------------------------------------------------------------
|
|
|
|
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",
|
|
}
|
|
|
|
GENDER_MODIFIERS: dict[str, str] = {
|
|
"male": "male, masculine build",
|
|
"female": "female, feminine build",
|
|
}
|
|
|
|
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",
|
|
},
|
|
}
|
|
|
|
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",
|
|
}
|
|
|
|
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 horse or beast, charging pose",
|
|
"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]] = {
|
|
"terrain": (1024, 512),
|
|
"biome_grid": (1024, 512),
|
|
"edges": (832, 512),
|
|
"units": (512, 512),
|
|
"buildings": (512, 512),
|
|
"resources": (512, 512),
|
|
"improvements": (512, 512),
|
|
"spells": (512, 512),
|
|
"ui": (256, 256),
|
|
}
|
|
|
|
_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.
|
|
|
|
SDXL weights early tokens most heavily, so composition order is:
|
|
1. SUBJECT FIRST — what the image depicts (name + visual description)
|
|
2. Category-specific attributes (combat type, school, race, keywords)
|
|
3. Style/perspective constraints (the style prefix)
|
|
4. Quality modifier
|
|
"""
|
|
dims = dimensions or {}
|
|
parts: list[str] = []
|
|
|
|
# 1 — SUBJECT FIRST (most important tokens for SDXL)
|
|
name = entity_data.get("name", "")
|
|
description = entity_data.get("description", "")
|
|
if name:
|
|
parts.append(name)
|
|
if description:
|
|
parts.append(description[:120])
|
|
|
|
# 2 — category-specific attributes
|
|
if category == "units":
|
|
combat_type = entity_data.get("combat_type", "")
|
|
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)
|
|
|
|
# school aesthetic
|
|
school = entity_data.get("school") or dims.get("school")
|
|
if school and school in SCHOOL_AESTHETICS:
|
|
parts.append(SCHOOL_AESTHETICS[school])
|
|
|
|
# 4 — race aesthetic
|
|
race = dims.get("race")
|
|
if race and race in RACE_AESTHETICS:
|
|
parts.append(RACE_AESTHETICS[race])
|
|
|
|
# gender modifier
|
|
gender = dims.get("gender")
|
|
if gender and gender in GENDER_MODIFIERS:
|
|
parts.append(GENDER_MODIFIERS[gender])
|
|
|
|
# 3 — STYLE PREFIX (perspective + art style constraints come after subject)
|
|
prefix = STYLE_PREFIXES.get(category, "")
|
|
if prefix:
|
|
parts.append(prefix)
|
|
|
|
# 4 — 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))
|