perf(climate-sim): ⚡ Optimize climate simulation rendering and engine runtime by refactoring WebGL shaders, terrain legend, and stats dashboard for faster performance; improve engine runner task processing and enhance sprite generation tooling with new prompts and database optimizations
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
9895dc5638
commit
2c10e94c1a
7 changed files with 153 additions and 137 deletions
|
|
@ -157,16 +157,15 @@ interface TerrainGroup {
|
|||
// Biome groups (Life mode chart) — groups biome_ids into visual categories
|
||||
// Must cover all ids in TERRAIN_ORDER (runner.ts)
|
||||
const BIOME_GROUPS: TerrainGroup[] = [
|
||||
{ label: 'Ocean', abbr: 'Ocn', ids: ['ocean', 'coast', 'deep_ocean', 'shallow_ocean', 'coral_reef', 'estuary', 'mangrove'], color: 'rgb(61,120,209)' },
|
||||
{ label: 'Fresh', abbr: 'Frs', ids: ['lake', 'pond', 'river', 'inland_sea'], color: 'rgb(100,160,230)' },
|
||||
{ label: 'Ice', abbr: 'Ice', ids: ['ice', 'snow', 'permanent_ice', 'polar_desert'], color: 'rgb(224,240,255)' },
|
||||
{ label: 'Ocean', abbr: 'Ocn', ids: ['deep_ocean', 'shallow_ocean', 'coral_reef', 'estuary', 'mangrove', 'ocean', 'coast', 'inland_sea'], color: 'rgb(61,120,209)' },
|
||||
{ label: 'Fresh', abbr: 'Frs', ids: ['lake', 'pond', 'river'], color: 'rgb(100,160,230)' },
|
||||
{ label: 'Ice', abbr: 'Ice', ids: ['permanent_ice', 'polar_desert', 'ice', 'snow'], color: 'rgb(224,240,255)' },
|
||||
{ label: 'Tundra', abbr: 'Tnd', ids: ['tundra', 'alpine_tundra'], color: 'rgb(184,194,166)' },
|
||||
{ label: 'Arid', abbr: 'Ard', ids: ['desert', 'chaparral', 'savanna'], color: 'rgb(222,199,128)' },
|
||||
{ label: 'Grass', abbr: 'Grs', ids: ['plains', 'grassland', 'temperate_grassland', 'alpine_meadow'], color: 'rgb(141,197,112)' },
|
||||
{ label: 'Forest', abbr: 'For', ids: ['forest', 'temperate_forest', 'boreal_forest', 'tropical_rainforest', 'tropical_dry_forest', 'temperate_rainforest', 'montane_forest', 'cloud_forest', 'jungle', 'enchanted_forest'], color: 'rgb(51,140,64)' },
|
||||
{ label: 'Rough', abbr: 'Rgh', ids: ['hills', 'mountains'], color: 'rgb(158,153,148)' },
|
||||
{ label: 'Grass', abbr: 'Grs', ids: ['temperate_grassland', 'alpine_meadow', 'plains', 'grassland'], color: 'rgb(141,197,112)' },
|
||||
{ label: 'Forest', abbr: 'For', ids: ['temperate_forest', 'boreal_forest', 'tropical_rainforest', 'tropical_dry_forest', 'montane_forest', 'cloud_forest', 'forest', 'jungle', 'temperate_rainforest', 'enchanted_forest'], color: 'rgb(51,140,64)' },
|
||||
{ label: 'Wetland', abbr: 'Wet', ids: ['swamp', 'bog'], color: 'rgb(61,79,36)' },
|
||||
{ label: 'Volcanic', abbr: 'Vol', ids: ['volcano'], color: 'rgb(191,51,20)' },
|
||||
{ label: 'Volcanic', abbr: 'Vol', ids: ['volcanic', 'volcano'], color: 'rgb(191,51,20)' },
|
||||
{ label: 'Cave', abbr: 'Cav', ids: ['subterranean'], color: 'rgb(90,70,60)' },
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ import styled from 'styled-components'
|
|||
// Only the first 31 (non-magic) entries are shown in the legend
|
||||
|
||||
const WATER_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
{ id: 'ocean', label: 'Ocean', rgb: [0.24, 0.47, 0.82] },
|
||||
{ id: 'deep_ocean', label: 'Deep Ocean', rgb: [0.12, 0.27, 0.63] },
|
||||
{ id: 'coast', label: 'Coast', rgb: [0.31, 0.71, 0.86] },
|
||||
{ id: 'coral_reef', label: 'Coral Reef', rgb: [0.25, 0.75, 0.67] },
|
||||
{ id: 'lake', label: 'Lake', rgb: [0.31, 0.63, 0.84] },
|
||||
{ id: 'inland_sea', label: 'Inland Sea', rgb: [0.25, 0.51, 0.80] },
|
||||
{ id: 'estuary', label: 'Estuary', rgb: [0.35, 0.59, 0.67] },
|
||||
{ id: 'deep_ocean', label: 'Deep Ocean', rgb: [0.12, 0.27, 0.63] },
|
||||
{ id: 'shallow_ocean', label: 'Shallow Ocean', rgb: [0.24, 0.47, 0.82] },
|
||||
{ id: 'coral_reef', label: 'Coral Reef', rgb: [0.25, 0.75, 0.67] },
|
||||
{ id: 'estuary', label: 'Estuary', rgb: [0.35, 0.59, 0.67] },
|
||||
{ id: 'lake', label: 'Lake', rgb: [0.31, 0.63, 0.84] },
|
||||
{ id: 'pond', label: 'Pond', rgb: [0.45, 0.70, 0.88] },
|
||||
{ id: 'river', label: 'River', rgb: [0.35, 0.60, 0.90] },
|
||||
{ id: 'mangrove', label: 'Mangrove', rgb: [0.28, 0.47, 0.35] },
|
||||
]
|
||||
|
||||
const ICE_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
{ id: 'ice', label: 'Ice', rgb: [0.88, 0.94, 1.00] },
|
||||
{ id: 'snow', label: 'Snow', rgb: [0.94, 0.96, 1.00] },
|
||||
{ id: 'polar_desert', label: 'Polar Desert', rgb: [0.78, 0.80, 0.73] },
|
||||
{ id: 'permanent_ice', label: 'Permanent Ice', rgb: [0.88, 0.94, 1.00] },
|
||||
{ id: 'polar_desert', label: 'Polar Desert', rgb: [0.78, 0.80, 0.73] },
|
||||
]
|
||||
|
||||
const COLD_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
|
|
@ -29,10 +29,8 @@ const COLD_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, numb
|
|||
|
||||
const TEMPERATE_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
{ id: 'chaparral', label: 'Chaparral', rgb: [0.69, 0.62, 0.42] },
|
||||
{ id: 'plains', label: 'Plains', rgb: [0.73, 0.82, 0.53] },
|
||||
{ id: 'grassland', label: 'Grassland', rgb: [0.38, 0.72, 0.35] },
|
||||
{ id: 'forest', label: 'Forest', rgb: [0.20, 0.55, 0.25] },
|
||||
{ id: 'temperate_rainforest', label: 'Temperate Rainforest', rgb: [0.10, 0.44, 0.20] },
|
||||
{ id: 'temperate_grassland', label: 'Temperate Grassland', rgb: [0.55, 0.77, 0.44] },
|
||||
{ id: 'temperate_forest', label: 'Temperate Forest', rgb: [0.20, 0.55, 0.25] },
|
||||
]
|
||||
|
||||
const TROPICAL_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
|
|
@ -40,16 +38,12 @@ const TROPICAL_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number,
|
|||
{ id: 'savanna', label: 'Savanna', rgb: [0.74, 0.70, 0.40] },
|
||||
{ id: 'tropical_dry_forest', label: 'Tropical Dry Forest', rgb: [0.50, 0.61, 0.25] },
|
||||
{ id: 'tropical_rainforest', label: 'Tropical Rainforest', rgb: [0.09, 0.45, 0.18] },
|
||||
{ id: 'jungle', label: 'Jungle', rgb: [0.14, 0.40, 0.14] },
|
||||
{ id: 'mangrove', label: 'Mangrove', rgb: [0.28, 0.47, 0.35] },
|
||||
]
|
||||
|
||||
const ELEVATION_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
{ id: 'hills', label: 'Hills', rgb: [0.58, 0.52, 0.38] },
|
||||
{ id: 'mountains', label: 'Mountains', rgb: [0.62, 0.60, 0.58] },
|
||||
{ id: 'alpine_meadow', label: 'Alpine Meadow', rgb: [0.56, 0.69, 0.47] },
|
||||
{ id: 'cloud_forest', label: 'Cloud Forest', rgb: [0.25, 0.47, 0.35] },
|
||||
{ id: 'montane_forest', label: 'Montane Forest', rgb: [0.18, 0.42, 0.28] },
|
||||
{ id: 'montane_forest', label: 'Montane Forest', rgb: [0.18, 0.42, 0.28] },
|
||||
{ id: 'cloud_forest', label: 'Cloud Forest', rgb: [0.25, 0.47, 0.35] },
|
||||
]
|
||||
|
||||
const WETLAND_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
|
|
@ -58,7 +52,8 @@ const WETLAND_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, n
|
|||
]
|
||||
|
||||
const SPECIAL_BIOMES: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> = [
|
||||
{ id: 'volcano', label: 'Volcano', rgb: [0.75, 0.20, 0.08] },
|
||||
{ id: 'volcanic', label: 'Volcanic', rgb: [0.75, 0.20, 0.08] },
|
||||
{ id: 'subterranean', label: 'Subterranean', rgb: [0.40, 0.32, 0.28] },
|
||||
]
|
||||
|
||||
const TERRAIN_SECTIONS: ReadonlyArray<{ label: string; biomes: ReadonlyArray<{ id: string; label: string; rgb: [number, number, number] }> }> = [
|
||||
|
|
|
|||
|
|
@ -26,21 +26,21 @@ uniform int uViewCenter; // 0 = equator, 1 = north pole, 2 = south pole
|
|||
uniform float uPolarRows; // how many rows from the pole are visible (mouse-wheel zoom)
|
||||
uniform float uTime;
|
||||
|
||||
// ── terrain colour lookup ──────────────────────────────────────────────────
|
||||
// Indices must match TERRAIN_ORDER in runner.ts (40 entries)
|
||||
// ── biome colour lookup ───────────────────────────────────────────────────
|
||||
// Indices must match TERRAIN_ORDER in runner.ts (41 entries)
|
||||
vec3 terrainColor(float encoded) {
|
||||
int idx = int(encoded * 39.0 + 0.5);
|
||||
// Water
|
||||
if (idx == 0) return vec3(0.24, 0.47, 0.82); // ocean
|
||||
if (idx == 1) return vec3(0.12, 0.27, 0.63); // deep_ocean
|
||||
if (idx == 2) return vec3(0.31, 0.71, 0.86); // coast
|
||||
if (idx == 3) return vec3(0.25, 0.75, 0.67); // coral_reef
|
||||
int idx = int(encoded * 40.0 + 0.5);
|
||||
// Aquatic biomes
|
||||
if (idx == 0) return vec3(0.12, 0.27, 0.63); // deep_ocean
|
||||
if (idx == 1) return vec3(0.24, 0.47, 0.82); // shallow_ocean
|
||||
if (idx == 2) return vec3(0.25, 0.75, 0.67); // coral_reef
|
||||
if (idx == 3) return vec3(0.35, 0.59, 0.67); // estuary
|
||||
if (idx == 4) return vec3(0.31, 0.63, 0.84); // lake
|
||||
if (idx == 5) return vec3(0.25, 0.51, 0.80); // inland_sea
|
||||
if (idx == 6) return vec3(0.35, 0.59, 0.67); // estuary
|
||||
if (idx == 5) return vec3(0.45, 0.70, 0.88); // pond
|
||||
if (idx == 6) return vec3(0.35, 0.60, 0.90); // river
|
||||
if (idx == 7) return vec3(0.28, 0.47, 0.35); // mangrove
|
||||
// Ice / Polar
|
||||
if (idx == 7) return vec3(0.88, 0.94, 1.00); // ice
|
||||
if (idx == 8) return vec3(0.94, 0.96, 1.00); // snow
|
||||
if (idx == 8) return vec3(0.88, 0.94, 1.00); // permanent_ice
|
||||
if (idx == 9) return vec3(0.78, 0.80, 0.73); // polar_desert
|
||||
// Cold
|
||||
if (idx == 10) return vec3(0.72, 0.76, 0.65); // tundra
|
||||
|
|
@ -48,37 +48,39 @@ vec3 terrainColor(float encoded) {
|
|||
if (idx == 12) return vec3(0.22, 0.44, 0.30); // boreal_forest
|
||||
// Temperate
|
||||
if (idx == 13) return vec3(0.69, 0.62, 0.42); // chaparral
|
||||
if (idx == 14) return vec3(0.73, 0.82, 0.53); // plains
|
||||
if (idx == 15) return vec3(0.38, 0.72, 0.35); // grassland
|
||||
if (idx == 16) return vec3(0.20, 0.55, 0.25); // forest
|
||||
if (idx == 17) return vec3(0.10, 0.44, 0.20); // temperate_rainforest
|
||||
// Warm / Tropical
|
||||
if (idx == 18) return vec3(0.87, 0.78, 0.50); // desert
|
||||
if (idx == 19) return vec3(0.74, 0.70, 0.40); // savanna
|
||||
if (idx == 20) return vec3(0.50, 0.61, 0.25); // tropical_dry_forest
|
||||
if (idx == 21) return vec3(0.09, 0.45, 0.18); // tropical_rainforest
|
||||
if (idx == 22) return vec3(0.14, 0.40, 0.14); // jungle
|
||||
if (idx == 23) return vec3(0.28, 0.47, 0.35); // mangrove
|
||||
if (idx == 14) return vec3(0.55, 0.77, 0.44); // temperate_grassland
|
||||
if (idx == 15) return vec3(0.20, 0.55, 0.25); // temperate_forest
|
||||
// Tropical
|
||||
if (idx == 16) return vec3(0.87, 0.78, 0.50); // desert
|
||||
if (idx == 17) return vec3(0.74, 0.70, 0.40); // savanna
|
||||
if (idx == 18) return vec3(0.50, 0.61, 0.25); // tropical_dry_forest
|
||||
if (idx == 19) return vec3(0.09, 0.45, 0.18); // tropical_rainforest
|
||||
// Elevation
|
||||
if (idx == 24) return vec3(0.58, 0.52, 0.38); // hills
|
||||
if (idx == 25) return vec3(0.62, 0.60, 0.58); // mountains
|
||||
if (idx == 26) return vec3(0.56, 0.69, 0.47); // alpine_meadow
|
||||
if (idx == 27) return vec3(0.25, 0.47, 0.35); // cloud_forest
|
||||
if (idx == 28) return vec3(0.18, 0.42, 0.28); // montane_forest
|
||||
if (idx == 20) return vec3(0.56, 0.69, 0.47); // alpine_meadow
|
||||
if (idx == 21) return vec3(0.18, 0.42, 0.28); // montane_forest
|
||||
if (idx == 22) return vec3(0.25, 0.47, 0.35); // cloud_forest
|
||||
// Wetland
|
||||
if (idx == 29) return vec3(0.24, 0.31, 0.14); // swamp
|
||||
if (idx == 30) return vec3(0.45, 0.39, 0.22); // bog
|
||||
if (idx == 23) return vec3(0.24, 0.31, 0.14); // swamp
|
||||
if (idx == 24) return vec3(0.45, 0.39, 0.22); // bog
|
||||
// Special
|
||||
if (idx == 31) return vec3(0.75, 0.20, 0.08); // volcano
|
||||
// Magic compat (hidden from legend)
|
||||
if (idx == 32) return vec3(0.42, 0.85, 0.55); // enchanted_forest
|
||||
if (idx == 33) return vec3(0.85, 0.70, 1.00); // mana_node
|
||||
if (idx == 34) return vec3(0.60, 0.95, 0.70); // ley_nexus
|
||||
if (idx == 35) return vec3(0.70, 0.70, 0.85); // lodestone_spire
|
||||
if (idx == 36) return vec3(0.75, 0.85, 1.00); // crystal_cavern
|
||||
if (idx == 37) return vec3(0.55, 0.40, 0.25); // worldroot
|
||||
if (idx == 38) return vec3(0.40, 0.80, 0.75); // primordial_spring
|
||||
return vec3(0.30, 0.50, 0.80); // abyssal_vortex (fallback)
|
||||
if (idx == 25) return vec3(0.75, 0.20, 0.08); // volcanic
|
||||
if (idx == 26) return vec3(0.40, 0.32, 0.28); // subterranean
|
||||
// Map gen legacy fallbacks (pre-classification)
|
||||
if (idx == 27) return vec3(0.24, 0.47, 0.82); // ocean → shallow_ocean color
|
||||
if (idx == 28) return vec3(0.31, 0.71, 0.86); // coast
|
||||
if (idx == 29) return vec3(0.88, 0.94, 1.00); // ice → permanent_ice color
|
||||
if (idx == 30) return vec3(0.94, 0.96, 1.00); // snow → ice color
|
||||
if (idx == 31) return vec3(0.73, 0.82, 0.53); // plains → grassland color
|
||||
if (idx == 32) return vec3(0.55, 0.77, 0.44); // grassland → temperate_grassland color
|
||||
if (idx == 33) return vec3(0.20, 0.55, 0.25); // forest → temperate_forest color
|
||||
if (idx == 34) return vec3(0.14, 0.40, 0.14); // jungle → tropical_rainforest color
|
||||
if (idx == 35) return vec3(0.58, 0.52, 0.38); // hills
|
||||
if (idx == 36) return vec3(0.62, 0.60, 0.58); // mountains
|
||||
if (idx == 37) return vec3(0.25, 0.51, 0.80); // inland_sea
|
||||
if (idx == 38) return vec3(0.10, 0.44, 0.20); // temperate_rainforest
|
||||
if (idx == 39) return vec3(0.42, 0.85, 0.55); // enchanted_forest
|
||||
if (idx == 40) return vec3(0.75, 0.20, 0.08); // volcano → volcanic color
|
||||
return vec3(0.30, 0.15, 0.40); // unknown (dark purple = visible error)
|
||||
}
|
||||
|
||||
// ── gradient helpers ───────────────────────────────────────────────────────
|
||||
|
|
@ -354,7 +356,7 @@ void main() {
|
|||
|
||||
// Marine health overlay (coast/ocean tiles only — reef > 0 is a proxy)
|
||||
if (showMarine) {
|
||||
int tidx = int(terrainEnc * 39.0 + 0.5);
|
||||
int tidx = int(terrainEnc * 40.0 + 0.5);
|
||||
if (tidx <= 3) {
|
||||
col = mix(col, marineColor(reef), 0.80);
|
||||
}
|
||||
|
|
@ -386,7 +388,7 @@ void main() {
|
|||
}
|
||||
if (showFish) {
|
||||
// Blue dots on water tiles with fish
|
||||
int tidx2 = int(terrainEnc * 39.0 + 0.5);
|
||||
int tidx2 = int(terrainEnc * 40.0 + 0.5);
|
||||
if (tidx2 <= 3 && reef > 0.01) {
|
||||
col = mix(col, vec3(0.20, 0.50, 0.95), 0.7 * reef);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
} from './types'
|
||||
import { GRID_WIDTH, GRID_HEIGHT, solarByRow } from './HexGrid'
|
||||
import { ClimatePhysics } from './ClimatePhysics.generated'
|
||||
import { EcologyPhysics } from './EcologyPhysics.generated'
|
||||
import { EcologyPhysics, classifyBiome } from './EcologyPhysics.generated'
|
||||
import { generate as generateMap } from './MapGenerator.generated'
|
||||
import { WORLD_SEED, DEFAULT_SCENARIO_TURNS } from './configs'
|
||||
|
||||
|
|
@ -50,26 +50,26 @@ function computeTileAlbedo(tile: TileState, isWater: boolean): number {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const TERRAIN_ORDER: readonly string[] = [
|
||||
// Water
|
||||
'ocean', 'deep_ocean', 'coast', 'coral_reef', 'lake', 'inland_sea', 'estuary',
|
||||
// Aquatic biomes (classifier outputs)
|
||||
'deep_ocean', 'shallow_ocean', 'coral_reef', 'estuary', 'lake', 'pond', 'river', 'mangrove',
|
||||
// Ice / Polar
|
||||
'ice', 'snow', 'polar_desert',
|
||||
'permanent_ice', 'polar_desert',
|
||||
// Cold
|
||||
'tundra', 'alpine_tundra', 'boreal_forest',
|
||||
// Temperate
|
||||
'chaparral', 'plains', 'grassland', 'forest', 'temperate_rainforest',
|
||||
// Warm / Tropical
|
||||
'desert', 'savanna', 'tropical_dry_forest', 'tropical_rainforest', 'jungle', 'mangrove',
|
||||
'chaparral', 'temperate_grassland', 'temperate_forest',
|
||||
// Tropical
|
||||
'desert', 'savanna', 'tropical_dry_forest', 'tropical_rainforest',
|
||||
// Elevation
|
||||
'hills', 'mountains', 'alpine_meadow', 'cloud_forest', 'montane_forest',
|
||||
'alpine_meadow', 'montane_forest', 'cloud_forest',
|
||||
// Wetland
|
||||
'swamp', 'bog',
|
||||
// Special
|
||||
'volcano',
|
||||
// Magic (hidden from legend — kept for ClimatePhysics.generated.ts compat)
|
||||
'enchanted_forest',
|
||||
'mana_node', 'ley_nexus', 'lodestone_spire', 'crystal_cavern',
|
||||
'worldroot', 'primordial_spring', 'abyssal_vortex',
|
||||
'volcanic', 'subterranean',
|
||||
// Map gen legacy IDs (pre-classification fallbacks)
|
||||
'ocean', 'coast', 'ice', 'snow', 'plains', 'grassland', 'forest', 'jungle',
|
||||
'hills', 'mountains', 'inland_sea', 'temperate_rainforest',
|
||||
'enchanted_forest', 'volcano',
|
||||
] as const
|
||||
|
||||
const TERRAIN_INDEX = new Map<string, number>(
|
||||
|
|
@ -302,6 +302,12 @@ export function runScenarioSync(
|
|||
|
||||
// Generate map from seed using the transpiled GDScript pipeline
|
||||
const grid = generateMap(worldSeed, GRID_WIDTH, GRID_HEIGHT, terrainCache, params, 'continents')
|
||||
|
||||
// Classify map gen terrain IDs into proper biome IDs
|
||||
for (const tile of grid.tiles) {
|
||||
tile.biome_id = classifyBiome(tile)
|
||||
}
|
||||
|
||||
const physics = new ClimatePhysics(params, terrainCache)
|
||||
const ecology = new EcologyPhysics()
|
||||
const snapshots: GridSnapshot[] = []
|
||||
|
|
|
|||
|
|
@ -32,15 +32,14 @@ STYLE_PREFIXES: dict[str, str] = {
|
|||
"biome_grid": _BG_STYLE,
|
||||
"edges": _BG_STYLE + ", transition gradient between two terrain types",
|
||||
|
||||
# --- LAYERS: UNITS (default — infantry on foot) ---
|
||||
# --- LAYERS: UNITS (default fallback — combat-type templates preferred) ---
|
||||
"units": (
|
||||
"on solid bright green chroma key background, "
|
||||
"single character game sprite, isometric view from above, "
|
||||
"character facing bottom-left, walking toward southwest, "
|
||||
"ONE person only, seen from elevated strategy game camera angle, "
|
||||
"like an Age of Empires II or Warcraft III unit sprite, "
|
||||
"painted fantasy game art, clean readable silhouette, "
|
||||
"clean crisp edges, masterpiece, best quality"
|
||||
"solid bright green screen background, "
|
||||
"character walking toward bottom-left corner of frame, facing southwest, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"DOTA 2 hero art style, bold painterly fantasy, rich saturated colors, "
|
||||
"single game unit sprite, ONE character only, full body visible head to feet, "
|
||||
"clean readable silhouette, masterpiece, best quality"
|
||||
),
|
||||
"buildings": (
|
||||
"isometric game building sprite, "
|
||||
|
|
@ -108,7 +107,7 @@ 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, multiple angles, character sheet, grey background",
|
||||
"units": _LAYER_NEG + ", multiple characters, crowd, group, turnaround, reference sheet, model sheet, concept art, multiple poses, multiple views, multiple angles, character sheet, grey background, white background, facing camera, facing forward, facing right, front view, side view, top-down view, photorealistic, photo, 3d render, 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",
|
||||
|
|
@ -223,56 +222,68 @@ SCHOOL_ENERGY_COLORS: dict[str, str] = {
|
|||
# Overrides the generic "units" STYLE_PREFIX when a combat_type matches
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_UNIT_GREEN_BG = "on solid bright green chroma key background"
|
||||
_UNIT_COMMON = (
|
||||
"ONE only, seen from elevated strategy game camera angle, "
|
||||
"painted fantasy game art, clean readable silhouette, "
|
||||
"clean crisp edges, masterpiece, best quality"
|
||||
# SDXL weights early tokens heaviest. Front-load the 5 weakest scoring dimensions:
|
||||
# 1. BRIGHT GREEN BG (background_compliance: 27%)
|
||||
# 2. FACING DIRECTION (facing_direction: 15%)
|
||||
# 3. CAMERA ANGLE (camera_angle: 25%)
|
||||
# 4. ART STYLE (art_style: 23%)
|
||||
# 5. Equipment comes from combat type template
|
||||
|
||||
_UNIT_CORE = (
|
||||
"solid bright green screen background, "
|
||||
"character walking toward bottom-left corner of frame, facing southwest, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"DOTA 2 hero art style, bold painterly fantasy, rich saturated colors, "
|
||||
"single game unit sprite, ONE character only, full body visible head to feet, "
|
||||
"clean readable silhouette, masterpiece, best quality"
|
||||
)
|
||||
|
||||
UNIT_STYLE_BY_COMBAT_TYPE: dict[str, str] = {
|
||||
"melee": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single armored warrior game sprite, isometric view from above, "
|
||||
f"character facing bottom-left walking southwest, {_UNIT_COMMON}"
|
||||
f"{_UNIT_CORE}, "
|
||||
"armored warrior holding melee weapon, heavy armor and shield"
|
||||
),
|
||||
"ranged": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single ranged fighter game sprite holding weapon ready, isometric view from above, "
|
||||
f"character facing bottom-left, {_UNIT_COMMON}"
|
||||
f"{_UNIT_CORE}, "
|
||||
"ranged fighter holding ranged weapon ready to fire, quiver or ammo pouch"
|
||||
),
|
||||
"cavalry": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single mounted rider on horse or war beast, isometric view from above, "
|
||||
f"riding toward bottom-left southwest, {_UNIT_COMMON}"
|
||||
"solid bright green screen background, "
|
||||
"mounted rider on armored warhorse walking toward bottom-left corner, facing southwest, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"DOTA 2 hero art style, bold painterly fantasy, rich saturated colors, "
|
||||
"single mounted unit sprite, ONE rider only, full mount visible, "
|
||||
"clean readable silhouette, masterpiece, best quality"
|
||||
),
|
||||
"siege": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single war machine or siege engine, isometric view from above, "
|
||||
f"facing bottom-left, no people, {_UNIT_COMMON}"
|
||||
"solid bright green screen background, "
|
||||
"war machine facing bottom-left corner of frame, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"DOTA 2 art style, bold painterly fantasy, "
|
||||
"single siege engine sprite, no people, heavy wood and iron, "
|
||||
"clean readable silhouette, masterpiece, best quality"
|
||||
),
|
||||
"flying": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single flying creature or aircraft in flight, isometric view from above, "
|
||||
f"flying toward bottom-left, wings spread, {_UNIT_COMMON}"
|
||||
"solid bright green screen background, "
|
||||
"flying creature soaring toward bottom-left corner, wings spread, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"DOTA 2 art style, bold painterly fantasy, "
|
||||
"single flying unit sprite, clean readable silhouette, masterpiece, best quality"
|
||||
),
|
||||
"marine": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single ship or boat on water, isometric view from above, "
|
||||
"small vessel game sprite like a Civilization V naval unit, "
|
||||
f"sailing toward bottom-left, {_UNIT_COMMON}"
|
||||
"solid bright green screen background, "
|
||||
"small ship sailing toward bottom-left corner of frame, "
|
||||
"seen from above at 45-degree elevated angle, "
|
||||
"Civilization V naval unit style, bold painterly, "
|
||||
"single vessel sprite, clean readable silhouette, masterpiece, best quality"
|
||||
),
|
||||
"civilian": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single civilian character game sprite, isometric view from above, "
|
||||
"character facing bottom-left walking southwest, carrying tools or supplies, "
|
||||
f"{_UNIT_COMMON}"
|
||||
f"{_UNIT_CORE}, "
|
||||
"civilian carrying tools or supplies, traveler clothes, no armor"
|
||||
),
|
||||
"specialist": (
|
||||
f"{_UNIT_GREEN_BG}, "
|
||||
"single support character game sprite, isometric view from above, "
|
||||
"character facing bottom-left, carrying specialized equipment, "
|
||||
f"{_UNIT_COMMON}"
|
||||
f"{_UNIT_CORE}, "
|
||||
"support character with specialized equipment, tactical gear"
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -467,16 +478,22 @@ def compose_prompt(
|
|||
) -> 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
|
||||
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.
|
||||
"""
|
||||
dims = dimensions or {}
|
||||
parts: list[str] = []
|
||||
|
||||
# 1 — SUBJECT FIRST (most important tokens for SDXL)
|
||||
# For units: STYLE PREFIX FIRST (green bg + direction + camera + art style)
|
||||
# These are the weakest scoring dimensions and need maximum SDXL weight.
|
||||
if category == "units":
|
||||
combat_type = entity_data.get("combat_type", "")
|
||||
prefix = get_unit_style(combat_type)
|
||||
if prefix:
|
||||
parts.append(prefix)
|
||||
|
||||
# Subject — what the image depicts (name + visual description)
|
||||
name = entity_data.get("name", "")
|
||||
description = entity_data.get("description", "")
|
||||
if name:
|
||||
|
|
@ -520,14 +537,11 @@ def compose_prompt(
|
|||
elif gender in GENDER_MODIFIERS:
|
||||
parts.append(GENDER_MODIFIERS[gender])
|
||||
|
||||
# 3 — STYLE PREFIX (perspective + art style constraints come after subject)
|
||||
if category == "units":
|
||||
combat_type = entity_data.get("combat_type", "")
|
||||
prefix = get_unit_style(combat_type)
|
||||
else:
|
||||
# 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)
|
||||
if prefix:
|
||||
parts.append(prefix)
|
||||
|
||||
# 4 — quality modifier
|
||||
quality = dims.get("quality")
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue