feat(ecology): Add dynamic flora physics interactions and update sprite generation pipeline

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-29 05:22:26 -07:00
parent d6af1519bb
commit e75d98e53d
5 changed files with 15 additions and 6 deletions

View file

@ -63,7 +63,7 @@ func process_turn(game_map: RefCounted) -> void:
var tiles: Array = game_map.tiles.values()
var o2: float = game_map.o2_fraction if "o2_fraction" in game_map else 0.21
tick_pioneer(tiles, _biome_flora, _veg)
tick_pioneer(tiles, _biome_flora, _veg, o2)
tick_canopy(tiles, _biome_flora, _veg, o2)
tick_undergrowth(tiles, _biome_flora, _veg, o2)
tick_fungi(tiles, _biome_flora, _veg)
@ -81,12 +81,16 @@ func process_turn(game_map: RefCounted) -> void:
# =========================================================================
static func tick_pioneer(tiles: Array, biome_flora: Dictionary, veg: Dictionary) -> void:
static func tick_pioneer(tiles: Array, biome_flora: Dictionary, veg: Dictionary, o2_fraction: float = 0.21) -> void:
## 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).
## Respects the O2 minimum threshold — no pioneer seeding in anoxic atmospheres.
var o2_mult: float = _o2_growth_mult(o2_fraction)
if o2_mult <= 0.0:
return
var pioneer_rate: float = veg.get("pioneer_rate", 0.002)
for tile: Variant in tiles:

View file

@ -391,12 +391,17 @@ function _getStage(stage_index: number, stages: Record<string, number>[]): Recor
// Flora tick functions (auto-transpiled from flora.gd)
// ---------------------------------------------------------------------------
function tickPioneer(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>): void {
function tickPioneer(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>, o2_fraction: number = 0.21): void {
// 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).
// Respects the O2 minimum threshold — no pioneer seeding in anoxic atmospheres.
let o2_mult = _o2GrowthMult(o2_fraction)
if (o2_mult <= 0.0) {
return
}
let pioneer_rate = (veg as any)["pioneer_rate"] ?? 0.002
for (const tile of tiles) {
@ -1160,7 +1165,7 @@ export class EcologyPhysics {
// Flora dynamics (order matches flora.gd process_turn)
const o2 = grid.o2_fraction ?? 0.21
tickPioneer(tiles, bf, veg)
tickPioneer(tiles, bf, veg, o2)
// 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

View file

@ -220,7 +220,7 @@ def _read_functions(path: Path) -> dict[str, str]:
# ---------------------------------------------------------------------------
FLORA_TICK_SIGS: dict[str, str] = {
"tick_pioneer": "tickPioneer(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>): void",
"tick_pioneer": "tickPioneer(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>, o2_fraction: number = 0.21): void",
"tick_canopy": "tickCanopy(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>, o2_fraction: number = 0.21): void",
"tick_undergrowth": "tickUndergrowth(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>, o2_fraction: number = 0.21): void",
"tick_fungi": "tickFungi(tiles: TileState[], biomeFlora: Record<string, Record<string, number>>, veg: Record<string, number>): void",
@ -657,7 +657,7 @@ export class EcologyPhysics {{
// Flora dynamics (order matches flora.gd process_turn)
const o2 = grid.o2_fraction ?? 0.21
tickPioneer(tiles, bf, veg)
tickPioneer(tiles, bf, veg, o2)
// 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