diff --git a/engine/src/modules/climate/climate.gd b/engine/src/modules/climate/climate.gd index 49027bd8..bcce6153 100644 --- a/engine/src/modules/climate/climate.gd +++ b/engine/src/modules/climate/climate.gd @@ -30,6 +30,7 @@ func process_turn(game_map: RefCounted, turn: int = 0, seed: int = 42) -> void: _ensure_ocean_dist(game_map) _collect_magic_forcing(game_map) + _apply_orbital_forcing(game_map, turn) _apply_aerosol_forcing(game_map) _update_temperatures(game_map) _update_lake_thermal_effects(game_map) @@ -38,7 +39,7 @@ func process_turn(game_map: RefCounted, turn: int = 0, seed: int = 42) -> void: _update_lake_evaporation(game_map) _update_deep_earth_water(game_map) _update_precipitation(game_map) - # Quality evolution removed — now owned by ecosystem.gd + _check_terrain_evolution(game_map) _tick_ley_residue(game_map) EcologicalEventsScript.process_events( game_map, turn, seed, _spec, DataLoader.get_ecological_events() @@ -58,6 +59,36 @@ func _collect_magic_forcing(_game_map: RefCounted) -> void: pass +# -- Step 1a: Orbital forcing (Milankovitch-like cycles) -- + + +func _apply_orbital_forcing(game_map: RefCounted, turn: int) -> void: + ## Superimpose deterministic orbital cycles onto the heat budget. + ## Three overlapping sinusoidal cycles model obliquity, precession, + ## and eccentricity at game timescale (1 turn = 10 years). + var t: float = float(turn) + var total_delta: float = 0.0 + for i: int in range(1, 4): + var prefix: String = "orbital_cycle_%d_" % i + var period: float = _params.get(prefix + "period", 0.0) + if period <= 0.0: + continue + var amplitude: float = _params.get(prefix + "amplitude", 0.0) + var phase: float = _params.get(prefix + "phase", 0.0) + total_delta += amplitude * sin(TAU * (t / period) + phase) + + if absf(total_delta) < 0.0001: + return + + # Warm cycles increase evaporation → more moisture. The coupling factor + # controls how strongly temperature oscillation drives moisture oscillation. + var moisture_coupling: float = _params.get("orbital_moisture_coupling", 0.5) + + for axial: Vector2i in game_map.tiles: + game_map.tiles[axial].magic_heat_delta += total_delta + game_map.tiles[axial].magic_moisture_delta += total_delta * moisture_coupling + + # -- Step 1b: Aerosol forcing (persistent stratospheric sulfate from volcanic/impact events) -- diff --git a/engine/src/modules/climate/climate_base.gd b/engine/src/modules/climate/climate_base.gd index c20731db..b0b4dddd 100644 --- a/engine/src/modules/climate/climate_base.gd +++ b/engine/src/modules/climate/climate_base.gd @@ -220,6 +220,37 @@ func _update_moisture_wind(game_map: RefCounted) -> void: # -- Step 8: Terrain quality evolution -- +func _check_terrain_evolution(game_map: RefCounted) -> void: + var up_thresh: int = int(_params.get("quality_up_threshold", _DEFAULTS["quality_up_threshold"])) + var down_thresh: int = int(_params.get("quality_down_threshold", _DEFAULTS["quality_down_threshold"])) + + for axial: Vector2i in game_map.tiles: + var tile: Variant = game_map.tiles[axial] + var tid: String = tile.biome_id + + if tile.is_natural_wonder: + continue + if tid == "ocean" or tid == "coast" or tid == "lake" or tid == "volcano": + continue + + var ideal: String = _ideal_terrain(tile) + + if ideal == tid: + tile.quality_progress += 1 + if tile.quality_progress >= up_thresh: + tile.quality_progress = 0 + if tile.quality < 5: + tile.quality += 1 + else: + tile.quality_progress -= 1 + if tile.quality_progress <= -down_thresh: + tile.quality_progress = 0 + if tile.quality > 1: + tile.quality -= 1 + else: + tile.biome_id = ideal + tile.quality = 1 + tile.quality_progress = 0 # -- Internal helpers --