magicciv/engine/src/modules/ecology/fauna_simplified.gd
2026-03-26 04:32:36 -07:00

128 lines
4.2 KiB
GDScript

class_name FaunaSimplified
extends RefCounted
## Tile-level fauna approximation for the guide (no individual creatures, no SQLite).
##
## Transpiler-friendly: static tick functions accept flat tile arrays + plain
## Dictionary params. No DataLoader, EventBus, preload, or HexUtils calls
## inside tick loops. Tiles accessed by col/row (not position Vector2i).
# =========================================================================
# Transpilable tick functions
# =========================================================================
static func tick_fish_stock(tiles: Array, marine_params: Dictionary) -> void:
## Logistic fish reproduction on water tiles.
## Seed empty water tiles at 10% capacity x tempMult.
var repro_rate: float = marine_params.get("reproduction_rate", 0.05)
var cap_base: float = marine_params.get("fish_capacity", 100.0)
var reef_bonus: float = marine_params.get("reef_bonus", 0.5)
var reef_penalty: float = marine_params.get("reef_penalty", -0.5)
var seed_fraction: float = marine_params.get("seed_fraction", 0.1)
for tile: Variant in tiles:
if not _is_water(tile):
continue
var temp_mult: float = _temp_mult(tile.temperature)
var cap: float = cap_base
if tile.reef_health > 0.5:
cap *= (1.0 + reef_bonus)
elif tile.reef_health < 0.1:
cap *= maxf(0.1, 1.0 + reef_penalty)
var stock: float = float(tile.fish_stock)
# Seed empty water tiles
if stock <= 0.0:
tile.fish_stock = int(cap * seed_fraction * temp_mult)
continue
var growth: float = repro_rate * temp_mult * stock * (1.0 - stock / cap)
tile.fish_stock = clampi(int(stock + growth), 0, int(cap))
static func tick_habitat_suitability(tiles: Array, w: int, h: int) -> void:
## Per land tile: average flora in radius-1 neighbors.
## undergrowth x 0.6 + canopy x 0.2 + fungi x 0.2.
for i: int in tiles.size():
var tile: Variant = tiles[i]
if _is_water(tile):
continue
var col: int = tile.col
var row: int = tile.row
var total_ug: float = tile.undergrowth
var total_ca: float = tile.canopy_cover
var total_fn: float = tile.fungi_network
var count: int = 1
# Radius-1 neighbors via even-q offset
var nb_offsets: Array = _get_neighbor_offsets(col)
for off: Variant in nb_offsets:
var nc: int = col + off[0]
var nr: int = row + off[1]
if nc < 0 or nc >= w or nr < 0 or nr >= h:
continue
var ni: int = nr * w + nc
if ni < 0 or ni >= tiles.size():
continue
var ntile: Variant = tiles[ni]
if _is_water(ntile):
continue
total_ug += ntile.undergrowth
total_ca += ntile.canopy_cover
total_fn += ntile.fungi_network
count += 1
if count > 0:
var avg_ug: float = total_ug / float(count)
var avg_ca: float = total_ca / float(count)
var avg_fn: float = total_fn / float(count)
tile.habitat_suitability = avg_ug * 0.6 + avg_ca * 0.2 + avg_fn * 0.2
else:
tile.habitat_suitability = 0.0
static func tick_reef_health(tiles: Array, marine_params: Dictionary) -> void:
## Reef growth in ideal temperature range.
var growth_rate: float = marine_params.get("reef_growth_rate", 0.02)
var ideal_min: float = marine_params.get("reef_ideal_min", 0.55)
var ideal_max: float = marine_params.get("reef_ideal_max", 0.75)
for tile: Variant in tiles:
if not _is_water(tile):
continue
if tile.temperature >= ideal_min and tile.temperature <= ideal_max:
tile.reef_health = minf(1.0, tile.reef_health + growth_rate)
# =========================================================================
# Pure static helpers
# =========================================================================
static func _temp_mult(temperature: float) -> float:
## Temperature multiplier for fish: tropical=1.0, temperate=0.8, polar=0.5.
if temperature > 0.55:
return 1.0
if temperature > 0.25:
return 0.8
return 0.5
static func _is_water(tile: Variant) -> bool:
if "substrate_id" in tile:
var sub: String = tile.substrate_id
return sub in ["deep_water", "shallow_water", "lake_bed"]
return tile.biome_id in ["ocean", "coast"]
static func _get_neighbor_offsets(col: int) -> Array:
## Even-q offset hex neighbor deltas as [dc, dr] arrays.
var parity: int = col & 1
if parity == 0:
return [[1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [0, 1]]
else:
return [[1, 1], [1, 0], [0, -1], [-1, 0], [-1, 1], [0, 1]]