feat(api-gdext): Add new simulation endpoints and Rust bindings for Godot Engine integration

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-06-06 17:46:38 -07:00
parent 8977f8f138
commit b56ae0612c

View file

@ -8423,6 +8423,91 @@ impl GdWorldSim {
n as i64
}
/// Load the three event-threshold configs from their canonical JSON pack
/// text (Rail 2). Each argument is the full file text; an empty string
/// keeps the current (Default) thresholds for that channel. Geological and
/// biological have authored JSONs (`geological_thresholds.json`,
/// `biological_events.json`); anomalous has none yet, so pass `""` to keep
/// Default. Returns `false` if any non-empty arg failed to parse (the
/// corresponding channel is left unchanged).
///
/// The live loop loads the production-rate JSONs (rare events). Proof scenes
/// / tests pass boosted-threshold JSON so events fire reliably in N turns.
#[func]
fn load_thresholds_from_json(
&mut self,
geo_json: GString,
bio_json: GString,
anomalous_json: GString,
) -> bool {
let mut ok = true;
let mut load = |text: GString, apply: &mut dyn FnMut(&serde_json::Value)| {
let s = text.to_string();
if s.is_empty() {
return;
}
match serde_json::from_str::<serde_json::Value>(&s) {
Ok(v) => apply(&v),
Err(e) => {
godot_error!("GdWorldSim::load_thresholds_from_json parse error: {e}");
ok = false;
}
}
};
load(geo_json, &mut |v| {
self.geo_thresholds = mc_mapgen::events::GeologicalThresholds::from_spec(v);
});
load(bio_json, &mut |v| {
self.bio_thresholds = mc_ecology::biological::BiologicalThresholds::from_spec(v);
});
load(anomalous_json, &mut |v| {
self.anomalous_thresholds = mc_climate::anomalous::AnomalousThresholds::from_spec(v);
});
ok
}
/// Run one continuous-worldsim event pass against the LIVE grid for the
/// playable turn loop (`turn_manager.gd`), which holds a `GdGridState`
/// (the shared climate/ecology grid) rather than a `GdGameState`.
///
/// Read-modify-WRITE: the live `GridState` is moved into a temporary
/// `GameState` shell, `dispatch_world_events` mutates it (eco-damage into
/// the owned `eco_map`, plus grid-side effects — `bloom_streak`
/// accumulation, geological elevation/movement deltas), then the mutated
/// grid is moved back into the `GdGridState`. This is critical: a
/// clone-and-discard would silently drop the grid mutations (bloom would
/// never accumulate, geological terrain effects would vanish) while
/// `eco_map` still grew — a false-positive.
///
/// The temporary `fog_map` is discarded (the live loop has no Rust-side
/// fog state to write back to — fog is GDScript-rendered). FogBank events
/// therefore have no in-game effect yet; wiring fog write-back is a
/// follow-up. Returns the number of events dispatched.
#[func]
fn dispatch_on_grid(&mut self, mut grid: Gd<GdGridState>, turn: i64, seed: i64) -> i64 {
let mut bound = grid.bind_mut();
// Move the live grid out via mem::replace with an empty 0×0 grid, run
// dispatch against a GameState shell, then move the mutated grid back.
let taken = std::mem::replace(&mut bound.inner, mc_core::grid::GridState::new(0, 0));
let mut shell = mc_state::game_state::GameState::default();
shell.grid = Some(taken);
shell.turn = turn.max(0) as u32;
let n = mc_worldsim::dispatch_world_events(
&mut shell,
seed as u64,
&self.geo_thresholds,
&self.bio_thresholds,
&self.anomalous_thresholds,
&mut self.eco_map,
&mut self.chronicle,
);
// Write the mutated grid back into the live GdGridState.
if let Some(g) = shell.grid.take() {
bound.inner = g;
}
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]