From dbb911038a407120076e27c7cb1fc128f7abe8f7 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 12:25:41 -0700 Subject: [PATCH] Persist active fire spread state --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/TechnicalDesignDocument.md | 7 +++ Scripts/verify_fire_persistence_state.py | 65 ++++++++++++++++++++++++ Source/AgrarianGame/AgrarianCampfire.cpp | 7 +++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_fire_persistence_state.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 667335d..8c9970c 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -843,7 +843,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Add server-authoritative fire spread rules for grass, brush, trees, shelters, and other burnable actors, including fuel, distance, wind, weather, and suppression hooks. Added replicated grass, forest, and structure fire intensities plus active spread radius that grow only on the server from nearby fuel, ignition distance, wind/weather, and a suppression-pressure hook for later rain, carried water, dirt/sand, firebreaks, and tools. - [x] Add fire maintenance gameplay so watched, cleared, contained, or extinguished fires are safe, while neglected fires can become dangerous. Updated lit campfire interaction to maintain the fire, added watch, clear-area, and contain-fire hooks, and made maintenance reduce campfire, vegetation, forest, and structure ignition risks while extinguishing resets active risk state. - [x] Add fire suppression hooks for rain, water carrying, dirt/sand, cleared firebreaks, and future firefighting tools. Added shared server-side suppression hooks plus water, dirt/sand, firebreak, and tool wrappers that raise suppression pressure, reduce ignition risks, reduce active fire intensity, shrink spread radius, and let rain/water drain fuel. -- [ ] Persist active grass, forest, and structure fires across save/load without corrupting world state. +- [x] Persist active grass, forest, and structure fires across save/load without corrupting world state. Extended campfire persistence coverage for ignition flags, ignition risk scores, active grass/forest/structure fire intensities, spread radius, and suppression pressure so save/load recovery preserves active and partially suppressed fire state. - [ ] Add QA coverage for safe campfires, unsafe campfires, vegetation spread, shelter ignition, suppression, and save/load recovery. - [ ] Add weather sounds. - [ ] Add wildlife sounds. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 72853ee..a01693b 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -384,6 +384,13 @@ shrinks spread radius, and lets water/rain also drain fuel. Wet weather contributes passive suppression pressure so rain and storms naturally slow dangerous fires before later UI and inventory actions call the same hooks. +Active grass, forest, and structure fire state persists with campfires through +the existing `UAgrarianPersistentActorComponent` provider path. Save records now +carry ignition flags, ignition risk scores, fire intensities, spread radius, and +suppression pressure so save/load recovery can restore active or partially +suppressed fires without resetting the world to a safe state or corrupting the +campfire's fuel, cooking, weather, and maintenance state. + Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`. Extinguishing clears remaining fuel, turns off replicated lit state, and reuses the same visual update path as natural fuel depletion. diff --git a/Scripts/verify_fire_persistence_state.py b/Scripts/verify_fire_persistence_state.py new file mode 100644 index 0000000..23890a1 --- /dev/null +++ b/Scripts/verify_fire_persistence_state.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Verify active grass, forest, and structure fire state persists.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FIRE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.cpp" +TDD = ROOT / "Docs" / "TechnicalDesignDocument.md" +ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" + +PERSISTED_KEYS = [ + "grass_ignition_risk_score", + "forest_ignition_risk_score", + "structure_ignition_risk_score", + "grass_or_brush_ignited", + "forest_fuel_ignited", + "structure_ignited", + "grass_fire_intensity", + "forest_fire_intensity", + "structure_fire_intensity", + "active_fire_spread_radius", + "fire_suppression_pressure", +] + + +def require(condition: bool, message: str) -> None: + if not condition: + raise SystemExit(f"FAILED: {message}") + + +def main() -> None: + fire_cpp = FIRE_CPP.read_text(encoding="utf-8") + tdd = TDD.read_text(encoding="utf-8") + roadmap = ROADMAP.read_text(encoding="utf-8") + + for key in PERSISTED_KEYS: + require( + f'NumberState.Add(TEXT("{key}")' in fire_cpp, + f"campfire capture missing persisted key {key}", + ) + require( + f'NumberState.Find(TEXT("{key}")' in fire_cpp, + f"campfire apply missing persisted key {key}", + ) + + for token in [ + "Active grass, forest, and structure fire state persists", + "ignition flags", + "fire intensities", + "spread radius", + "suppression pressure", + ]: + require(token in tdd, f"technical design document missing {token!r}") + + require( + "[x] Persist active grass, forest, and structure fires across save/load" in roadmap, + "roadmap item is not checked off", + ) + + print("OK: active fire state persists across save/load.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianCampfire.cpp b/Source/AgrarianGame/AgrarianCampfire.cpp index 68b9b0c..d363593 100644 --- a/Source/AgrarianGame/AgrarianCampfire.cpp +++ b/Source/AgrarianGame/AgrarianCampfire.cpp @@ -231,6 +231,7 @@ void AAgrarianCampfire::CapturePersistentState_Implementation(UAgrarianPersisten PersistentComponent->NumberState.Add(TEXT("forest_fire_intensity"), ForestFireIntensity); PersistentComponent->NumberState.Add(TEXT("structure_fire_intensity"), StructureFireIntensity); PersistentComponent->NumberState.Add(TEXT("active_fire_spread_radius"), ActiveFireSpreadRadius); + PersistentComponent->NumberState.Add(TEXT("fire_suppression_pressure"), FireSuppressionPressure); } void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent) @@ -260,6 +261,7 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA const float* SavedForestFireIntensity = PersistentComponent->NumberState.Find(TEXT("forest_fire_intensity")); const float* SavedStructureFireIntensity = PersistentComponent->NumberState.Find(TEXT("structure_fire_intensity")); const float* SavedActiveFireSpreadRadius = PersistentComponent->NumberState.Find(TEXT("active_fire_spread_radius")); + const float* SavedFireSuppressionPressure = PersistentComponent->NumberState.Find(TEXT("fire_suppression_pressure")); if (SavedFuelSeconds) { @@ -356,6 +358,11 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA ActiveFireSpreadRadius = FMath::Clamp(*SavedActiveFireSpreadRadius, 0.0f, MaxFireSpreadRadius); } + if (SavedFireSuppressionPressure) + { + FireSuppressionPressure = FMath::Clamp(*SavedFireSuppressionPressure, 0.0f, 1.0f); + } + SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f); }