feat(api-gdext): ✨ Introduce worldsim dynamics traits, structs, and functions for Godot engine extension API
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
8bc770d8a0
commit
061b6f0f7b
1 changed files with 182 additions and 0 deletions
|
|
@ -798,6 +798,62 @@ impl GdFaunaEcology {
|
|||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Serialize the live per-tile fauna population map to a JSON string for
|
||||
/// save persistence (Increment 2 — closes the `EcologyState.gd` "save not
|
||||
/// round-tripped" gap). `tile_populations` is a `BTreeMap` keyed by an
|
||||
/// `(i32, i32)` tuple, so the output is a deterministic JSON array of
|
||||
/// `[[col, row], [slots…]]` pairs (JSON object keys must be strings).
|
||||
///
|
||||
/// Only the populations are persisted — the species registry is rebuilt
|
||||
/// from the canonical JSON pack on load (`EcologyState.gd::reset` +
|
||||
/// `_ensure_species_registered`), and the restored slots reference those
|
||||
/// species by numeric id.
|
||||
#[func]
|
||||
fn tile_populations_to_json(&self) -> GString {
|
||||
let pairs: Vec<(&(i32, i32), &Vec<PopulationSlot>)> =
|
||||
self.inner.tile_populations.iter().collect();
|
||||
match serde_json::to_string(&pairs) {
|
||||
Ok(s) => s.into(),
|
||||
Err(e) => {
|
||||
godot_error!("GdFaunaEcology::tile_populations_to_json: {e}");
|
||||
GString::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the per-tile fauna population map from a JSON string produced by
|
||||
/// `tile_populations_to_json`. Replaces the current map. Returns `false` on
|
||||
/// parse failure (the existing map is left untouched).
|
||||
///
|
||||
/// The caller MUST register the species library first (so the restored
|
||||
/// slots' `species_id`s resolve during `tick_populations`); the standard
|
||||
/// load path does this via `EcologyState.gd::_ensure_species_registered`.
|
||||
#[func]
|
||||
fn restore_tile_populations_from_json(&mut self, json: GString) -> bool {
|
||||
let s = json.to_string();
|
||||
if s.is_empty() {
|
||||
return false;
|
||||
}
|
||||
match serde_json::from_str::<Vec<((i32, i32), Vec<PopulationSlot>)>>(&s) {
|
||||
Ok(pairs) => {
|
||||
self.inner.tile_populations = pairs.into_iter().collect();
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
godot_error!("GdFaunaEcology::restore_tile_populations_from_json: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of distinct tiles that currently hold any fauna population.
|
||||
/// Cheap progress probe for tests / HUD (distinct from
|
||||
/// `population_slot_count`, which sums slots across tiles).
|
||||
#[func]
|
||||
fn populated_tile_count(&self) -> i64 {
|
||||
self.inner.tile_populations.len() as i64
|
||||
}
|
||||
}
|
||||
|
||||
// ── GdAtmosphericChemistry ──────────────────────────────────────────────
|
||||
|
|
@ -8296,3 +8352,129 @@ impl GdVision {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ── GdWorldSim ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Continuous-worldsim event bridge (Increment 2). Owns the per-tile
|
||||
// eco-damage accumulator (`eco_map`), a `Chronicle` of world events, and the
|
||||
// three event-threshold configs. It does NOT run `TurnProcessor::step` — in
|
||||
// the playable path GDScript drives turn advancement (`turn_manager.gd`) and
|
||||
// the per-turn climate + ecology ticks already run via `GdClimatePhysics` and
|
||||
// `GdFaunaEcology`. This bridge adds the remaining worldsim layer:
|
||||
// `dispatch_world_events` (geological / biological / anomalous + fog) and the
|
||||
// owned `eco_map`, both keyed to the *same* `GdGameState` the turn loop owns.
|
||||
//
|
||||
// Population migration is NOT here — it is a continuous ecology tick and lives
|
||||
// inside `mc_ecology::EcologyEngine::process_step`, so it already runs through
|
||||
// `GdFaunaEcology::tick_populations`.
|
||||
//
|
||||
// Save/load: `eco_map` round-trips through `eco_map_to_json` /
|
||||
// `restore_eco_map_from_json` (a `BTreeMap` → deterministic byte order). The
|
||||
// live population map round-trips on `GdFaunaEcology` (it owns it).
|
||||
|
||||
/// Continuous-worldsim event bridge. Holds the per-tile eco-damage map,
|
||||
/// a world-event chronicle, and the event thresholds. Default thresholds
|
||||
/// match the Rust `Default` impls; JSON-loaded configs are a later increment.
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=RefCounted)]
|
||||
pub struct GdWorldSim {
|
||||
eco_map: std::collections::BTreeMap<(u16, u16), mc_ecology::tile::TileEcoState>,
|
||||
chronicle: mc_turn::chronicle::Chronicle,
|
||||
geo_thresholds: mc_mapgen::events::GeologicalThresholds,
|
||||
bio_thresholds: mc_ecology::biological::BiologicalThresholds,
|
||||
anomalous_thresholds: mc_climate::anomalous::AnomalousThresholds,
|
||||
base: Base<RefCounted>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl IRefCounted for GdWorldSim {
|
||||
fn init(base: Base<RefCounted>) -> Self {
|
||||
Self {
|
||||
eco_map: std::collections::BTreeMap::new(),
|
||||
chronicle: mc_turn::chronicle::Chronicle::new(),
|
||||
geo_thresholds: mc_mapgen::events::GeologicalThresholds::default(),
|
||||
bio_thresholds: mc_ecology::biological::BiologicalThresholds::default(),
|
||||
anomalous_thresholds: mc_climate::anomalous::AnomalousThresholds::default(),
|
||||
base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl GdWorldSim {
|
||||
/// Run one continuous-worldsim event pass against `state` for the current
|
||||
/// turn: geological / biological / anomalous event derivation, eco-damage
|
||||
/// accumulation into the owned `eco_map`, fog-bank application, and one
|
||||
/// chronicle entry per event. Returns the number of events dispatched.
|
||||
///
|
||||
/// `seed` is the game master seed (the caller passes `GameState.map_seed`).
|
||||
/// Deterministic: the same `(state, seed, turn)` always produces the same
|
||||
/// events and eco-map mutations.
|
||||
///
|
||||
/// No-op (returns 0) when `state` has no grid.
|
||||
#[func]
|
||||
fn dispatch(&mut self, mut state: Gd<GdGameState>, seed: i64) -> i64 {
|
||||
let mut bound = state.bind_mut();
|
||||
let n = mc_worldsim::dispatch_world_events(
|
||||
&mut bound.inner,
|
||||
seed as u64,
|
||||
&self.geo_thresholds,
|
||||
&self.bio_thresholds,
|
||||
&self.anomalous_thresholds,
|
||||
&mut self.eco_map,
|
||||
&mut self.chronicle,
|
||||
);
|
||||
n as i64
|
||||
}
|
||||
|
||||
/// Total number of world-event entries accumulated in the chronicle since
|
||||
/// construction (or last restore). Cheap progress probe for tests / HUD.
|
||||
#[func]
|
||||
fn chronicle_len(&self) -> i64 {
|
||||
self.chronicle.len() as i64
|
||||
}
|
||||
|
||||
/// Number of tiles that carry any accumulated eco-damage.
|
||||
#[func]
|
||||
fn eco_map_len(&self) -> i64 {
|
||||
self.eco_map.len() as i64
|
||||
}
|
||||
|
||||
/// Serialize the eco-damage map to a JSON string for save persistence.
|
||||
/// `BTreeMap` iteration is sorted, so the bytes are deterministic. The
|
||||
/// `(u16, u16)` tuple keys are emitted as a JSON array of `[key, value]`
|
||||
/// pairs (JSON object keys must be strings).
|
||||
#[func]
|
||||
fn eco_map_to_json(&self) -> GString {
|
||||
let pairs: Vec<(&(u16, u16), &mc_ecology::tile::TileEcoState)> =
|
||||
self.eco_map.iter().collect();
|
||||
match serde_json::to_string(&pairs) {
|
||||
Ok(s) => s.into(),
|
||||
Err(e) => {
|
||||
godot_error!("GdWorldSim::eco_map_to_json: {e}");
|
||||
GString::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the eco-damage map from a JSON string produced by
|
||||
/// `eco_map_to_json`. Replaces the current map. Returns `false` on parse
|
||||
/// failure (the existing map is left untouched).
|
||||
#[func]
|
||||
fn restore_eco_map_from_json(&mut self, json: GString) -> bool {
|
||||
let s = json.to_string();
|
||||
if s.is_empty() {
|
||||
return false;
|
||||
}
|
||||
match serde_json::from_str::<Vec<((u16, u16), mc_ecology::tile::TileEcoState)>>(&s) {
|
||||
Ok(pairs) => {
|
||||
self.eco_map = pairs.into_iter().collect();
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
godot_error!("GdWorldSim::restore_eco_map_from_json: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue