feat(ecology-specific): ✨ Update flora simulation logic and sprite generation tooling for ecology module
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
c5d0f3c1ea
commit
d6af1519bb
5 changed files with 68 additions and 24 deletions
|
|
@ -82,28 +82,38 @@ func process_turn(game_map: RefCounted) -> void:
|
|||
|
||||
|
||||
static func tick_pioneer(tiles: Array, biome_flora: Dictionary, veg: Dictionary) -> void:
|
||||
## Pioneer colonization: allows bare ground to acquire initial flora.
|
||||
## Applies a small spontaneous seeding rate (additive, not multiplicative) to
|
||||
## overcome the population gate in tick_undergrowth. Models bacteria/lichen/spore
|
||||
## colonization of virgin substrate. Does NOT run for abiotic worlds (ecology
|
||||
## is disabled entirely when abioticWorld = true in the scenario config).
|
||||
## Pioneer colonization: seeds bare ground with initial flora using raw climate values.
|
||||
## Uses temperature/moisture directly (not biome climate range) because abiotic-classified
|
||||
## tiles may have narrow climate ranges that don't match their actual climate conditions
|
||||
## (e.g., a temperate tile classified as polar_desert before biology establishes).
|
||||
## Does NOT run for abiotic worlds (ecology is disabled when abioticWorld = true).
|
||||
var pioneer_rate: float = veg.get("pioneer_rate", 0.002)
|
||||
|
||||
for tile: Variant in tiles:
|
||||
if BiomeRegistry.has_tag(tile.biome_id, "is_water"):
|
||||
continue
|
||||
if tile.undergrowth > 0.0:
|
||||
# Only seed completely bare tiles
|
||||
if tile.undergrowth > 0.0 and tile.canopy_cover > 0.0:
|
||||
continue
|
||||
# Raw habitability: minimum conditions for pioneer life
|
||||
if tile.temperature < 0.10 or tile.moisture < 0.15:
|
||||
continue
|
||||
var bf: Dictionary = biome_flora.get(tile.biome_id, {})
|
||||
if bf.is_empty():
|
||||
continue
|
||||
var climax_ug: float = bf.get("undergrowth", 0.0)
|
||||
# No seeding for biomes with negligible undergrowth potential (desert, ice)
|
||||
if climax_ug <= 0.01:
|
||||
# Habitat quality: scale by how far above survival thresholds, capped at 1
|
||||
var hab_temp: float = minf(1.0, (tile.temperature - 0.10) / 0.40)
|
||||
var hab_moist: float = minf(1.0, (tile.moisture - 0.15) / 0.40)
|
||||
var hab: float = hab_temp * hab_moist
|
||||
if hab <= 0.0:
|
||||
continue
|
||||
var match_mult: float = _climate_match_flat(tile, bf)
|
||||
if match_mult > 0.0:
|
||||
tile.undergrowth = pioneer_rate * match_mult
|
||||
# Seed undergrowth on bare tiles
|
||||
if tile.undergrowth <= 0.0:
|
||||
tile.undergrowth = pioneer_rate * hab
|
||||
# Seed canopy on tiles with tree/shrub potential (climax canopy >= 5%)
|
||||
var climax_ca: float = bf.get("canopy", 0.0)
|
||||
if climax_ca >= 0.05 and tile.canopy_cover <= 0.0 and tile.undergrowth >= pioneer_rate * 0.5:
|
||||
tile.canopy_cover = pioneer_rate * 0.5 * hab
|
||||
|
||||
|
||||
static func tick_canopy(tiles: Array, biome_flora: Dictionary, veg: Dictionary, o2_fraction: float = 0.21) -> void:
|
||||
|
|
|
|||
|
|
@ -392,32 +392,44 @@ function _getStage(stage_index: number, stages: Record<string, number>[]): Recor
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
function tickPioneer(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>): void {
|
||||
// Pioneer colonization: allows bare ground to acquire initial flora.
|
||||
// Applies a small spontaneous seeding rate (additive, not multiplicative) to
|
||||
// overcome the population gate in tick_undergrowth. Models bacteria/lichen/spore
|
||||
// colonization of virgin substrate. Does NOT run for abiotic worlds (ecology
|
||||
// is disabled entirely when abioticWorld = true in the scenario config).
|
||||
// Pioneer colonization: seeds bare ground with initial flora using raw climate values.
|
||||
// Uses temperature/moisture directly (not biome climate range) because abiotic-classified
|
||||
// tiles may have narrow climate ranges that don't match their actual climate conditions
|
||||
// (e.g., a temperate tile classified as polar_desert before biology establishes).
|
||||
// Does NOT run for abiotic worlds (ecology is disabled when abioticWorld = true).
|
||||
let pioneer_rate = (veg as any)["pioneer_rate"] ?? 0.002
|
||||
|
||||
for (const tile of tiles) {
|
||||
if (hasTag(tile.biome_id, "is_water")) {
|
||||
continue
|
||||
}
|
||||
if (tile.undergrowth > 0.0) {
|
||||
// Only seed completely bare tiles
|
||||
if (tile.undergrowth > 0.0 && tile.canopy_cover > 0.0) {
|
||||
continue
|
||||
}
|
||||
// Raw habitability: minimum conditions for pioneer life
|
||||
if (tile.temperature < 0.10 || tile.moisture < 0.15) {
|
||||
continue
|
||||
}
|
||||
let bf = biomeFlora[tile.biome_id] ?? {}
|
||||
if (Object.keys(bf).length === 0) {
|
||||
continue
|
||||
}
|
||||
let climax_ug = bf["undergrowth"] ?? 0.0
|
||||
// No seeding for biomes with negligible undergrowth potential (desert, ice)
|
||||
if (climax_ug <= 0.01) {
|
||||
// Habitat quality: scale by how far above survival thresholds, capped at 1
|
||||
let hab_temp = Math.min(1.0, (tile.temperature - 0.10) / 0.40)
|
||||
let hab_moist = Math.min(1.0, (tile.moisture - 0.15) / 0.40)
|
||||
let hab = hab_temp * hab_moist
|
||||
if (hab <= 0.0) {
|
||||
continue
|
||||
}
|
||||
let match_mult = _climateMatchFlat(tile, bf)
|
||||
if (match_mult > 0.0) {
|
||||
tile.undergrowth = pioneer_rate * match_mult
|
||||
// Seed undergrowth on bare tiles
|
||||
if (tile.undergrowth <= 0.0) {
|
||||
tile.undergrowth = pioneer_rate * hab
|
||||
}
|
||||
// Seed canopy on tiles with tree/shrub potential (climax canopy >= 5%)
|
||||
let climax_ca = bf["canopy"] ?? 0.0
|
||||
if (climax_ca >= 0.05 && tile.canopy_cover <= 0.0 && tile.undergrowth >= pioneer_rate * 0.5) {
|
||||
tile.canopy_cover = pioneer_rate * 0.5 * hab
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1149,6 +1161,17 @@ export class EcologyPhysics {
|
|||
// Flora dynamics (order matches flora.gd process_turn)
|
||||
const o2 = grid.o2_fraction ?? 0.21
|
||||
tickPioneer(tiles, bf, veg)
|
||||
// Reclassify pioneer-seeded tiles immediately so subsequent ticks use the correct biome.
|
||||
// A tile seeded from canopy=0 to canopy>0 may be in an abiotic biome (polar_desert,
|
||||
// chaparral) that won't match its actual climate, causing tick_canopy/tick_undergrowth
|
||||
// to decay the pioneer growth. Reclassifying here lets the same-turn tick functions
|
||||
// use the correct biotic biome and grow instead of decay.
|
||||
for (const tile of tiles) {
|
||||
if (!hasTag(tile.biome_id, 'is_water') && (tile.undergrowth > 0 || tile.canopy_cover > 0)) {
|
||||
const _newBiome = _classifyBiomeInline(tile)
|
||||
if (_newBiome !== tile.biome_id) tile.biome_id = _newBiome
|
||||
}
|
||||
}
|
||||
tickCanopy(tiles, bf, veg, o2)
|
||||
tickUndergrowth(tiles, bf, veg, o2)
|
||||
tickFungi(tiles, bf, veg)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -658,6 +658,17 @@ export class EcologyPhysics {{
|
|||
// Flora dynamics (order matches flora.gd process_turn)
|
||||
const o2 = grid.o2_fraction ?? 0.21
|
||||
tickPioneer(tiles, bf, veg)
|
||||
// Reclassify pioneer-seeded tiles immediately so subsequent ticks use the correct biome.
|
||||
// A tile seeded from canopy=0 to canopy>0 may be in an abiotic biome (polar_desert,
|
||||
// chaparral) that won't match its actual climate, causing tick_canopy/tick_undergrowth
|
||||
// to decay the pioneer growth. Reclassifying here lets the same-turn tick functions
|
||||
// use the correct biotic biome and grow instead of decay.
|
||||
for (const tile of tiles) {{
|
||||
if (!hasTag(tile.biome_id, 'is_water') && (tile.undergrowth > 0 || tile.canopy_cover > 0)) {{
|
||||
const _newBiome = _classifyBiomeInline(tile)
|
||||
if (_newBiome !== tile.biome_id) tile.biome_id = _newBiome
|
||||
}}
|
||||
}}
|
||||
tickCanopy(tiles, bf, veg, o2)
|
||||
tickUndergrowth(tiles, bf, veg, o2)
|
||||
tickFungi(tiles, bf, veg)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue