From 43709243868ecc461ce7c7e735624baf2125860d Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 14:00:10 -0700 Subject: [PATCH] Add full day night survival QA gate --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/QA/MvpQaGates.md | 29 +++++++ .../verify_full_day_night_survival_qa_gate.py | 85 +++++++++++++++++++ Scripts/verify_stat_save_load_support.py | 4 +- 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 Scripts/verify_full_day_night_survival_qa_gate.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 0f81e7f..cfcd46f 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -860,7 +860,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Can gather resources. Added a resource gathering QA gate tied to Ground Zero wood/fiber nodes, server-authoritative resource interaction, replicated harvest depletion, inventory grants, resource persistence coverage, and the natural shelter playable-loop smoke test. - [x] Can craft a fire. Added a craft-fire QA gate tied to `DA_Recipe_Campfire`, the player recipe setup, `BP_Campfire`, replicated campfire lit/fuel state, fire interaction prompts, campfire persistence, and fire-risk QA coverage. - [x] Can craft a shelter. Added a craft-shelter QA gate tied to primitive frame/wall/roof/shelter recipes, native build placement, `BP_PrimitiveShelter`, shelter persistence/protection hooks, and the natural shelter playable-loop smoke test. -- [ ] Can survive one full day/night cycle. +- [x] Can survive one full day/night cycle. Added a full day/night survival QA gate tied to the `4 real hours = 1 in-game day` calendar, replicated world time and solar phase, authoritative hunger/thirst/stamina/body-temperature/health pressure, fire and shelter mitigation, critical survival HUD visibility, and save/load persistence coverage before investor demos treat the gate as play-proven. - [ ] Can die from survival pressure. - [ ] Can reconnect and retain state. - [ ] Can restart server and retain placed shelter. diff --git a/Docs/QA/MvpQaGates.md b/Docs/QA/MvpQaGates.md index 45b65ab..7657c06 100644 --- a/Docs/QA/MvpQaGates.md +++ b/Docs/QA/MvpQaGates.md @@ -125,3 +125,32 @@ Required evidence: This gate is gameplay/server-relevant and should be covered by the next server package deployment. + +## Full Day/Night Survival + +The full day/night survival gate proves one player can remain alive through one +complete compressed Agrarian day while the server advances time, weather, and +survival pressure. + +MVP timing: + +- The gameplay calendar target is `4 real hours = 1 in-game day`. +- `AAgrarianGameState` advances and replicates `WorldHours`, day/year state, + solar bounds, `IsNight`, weather, and ambient temperature. +- The pass starts at a known hour, preferably dawn or morning, and ends after + `WorldHours` reaches the same solar phase on the next in-game day. + +Required evidence: + +- Hunger, thirst, stamina, body temperature, and health update over time on + server authority. +- A player can gather resources, craft or use a fire, craft or use shelter, + and recover enough food/water/warmth to avoid death for the full cycle. +- Day/night state changes at least once and returns to the original phase. +- The critical survival HUD can show alive/dead state and core stat pressure. +- World time and player survival state are covered by save/load persistence. +- The result is recorded as either a completed manual run or a captured test + log before external MVP demos treat the gate as fully play-proven. + +This gate is gameplay/server-relevant and should be covered by the next server +package deployment. diff --git a/Scripts/verify_full_day_night_survival_qa_gate.py b/Scripts/verify_full_day_night_survival_qa_gate.py new file mode 100644 index 0000000..6b4ada9 --- /dev/null +++ b/Scripts/verify_full_day_night_survival_qa_gate.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Verify the MVP full day/night survival QA gate is covered.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" +QA_DOC = ROOT / "Docs" / "QA" / "MvpQaGates.md" +CORE_DESIGN = ROOT / "Docs" / "CoreDesignDocument.md" +MOVEMENT_TIME = ROOT / "Docs" / "MovementAndTimeScaleBaseline.md" +GAME_STATE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.h" +GAME_STATE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.cpp" +SURVIVAL_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp" +DEBUG_HUD_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp" +WORLD_TIME_VERIFY = ROOT / "Scripts" / "verify_world_time_persistence.py" +STAT_SAVE_VERIFY = ROOT / "Scripts" / "verify_stat_save_load_support.py" +SHELTER_VERIFY = ROOT / "Scripts" / "verify_shelter_weather_protection.py" +FIRE_VERIFY = ROOT / "Scripts" / "verify_craft_fire_qa_gate.py" + +REQUIRED = { + QA_DOC: [ + "## Full Day/Night Survival", + "4 real hours = 1 in-game day", + "WorldHours", + "Day/night state changes at least once", + "Hunger, thirst, stamina, body temperature, and health", + ], + CORE_DESIGN: [ + "gameplay calendar target: `4 real hours = 1 in-game day`", + "Solve thirst, hunger, warmth, and shelter.", + ], + MOVEMENT_TIME: [ + "Calendar compression is for crop growth, weather passage, day/night rhythm", + "hunger, thirst, weather exposure, and stamina", + ], + GAME_STATE_H: [ + "float GameHoursPerRealMinute = 0.1f;", + "float WorldHours = 8.0f;", + "bool IsNight() const;", + ], + GAME_STATE_CPP: [ + "WorldHours += (DeltaSeconds / 60.0f) * GameHoursPerRealMinute;", + "while (WorldHours >= 24.0f)", + "DOREPLIFETIME(AAgrarianGameState, WorldHours);", + "bool AAgrarianGameState::IsNight() const", + ], + SURVIVAL_CPP: [ + "Survival.Hunger -= HungerDecayPerMinute * Minutes;", + "Survival.Thirst -= ThirstDecayPerMinute * Minutes;", + "Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;", + "Survival.BodyTemperature += FMath::Clamp(ExposureDelta", + "Survival.Health -= StarvationDamagePerMinute * Minutes;", + "Survival.Health -= DehydrationDamagePerMinute * Minutes;", + "Survival.Health -= ColdDamagePerMinute * Minutes", + ], + DEBUG_HUD_CPP: [ + "DrawCriticalStats", + "State ALIVE", + "State DEAD", + ], + WORLD_TIME_VERIFY: ["WorldHours", "SaveGame->WorldHours"], + STAT_SAVE_VERIFY: ["SurvivalComponent->ApplySavedState", "SavedPlayer.Survival"], + SHELTER_VERIFY: ["weather protection"], + FIRE_VERIFY: ["## Craft Fire", "AAgrarianCampfire"], + ROADMAP: [ + "[x] Can survive one full day/night cycle.", + ], +} + + +def main() -> None: + missing: list[str] = [] + for path, snippets in REQUIRED.items(): + text = path.read_text(encoding="utf-8") + for snippet in snippets: + if snippet not in text: + missing.append(f"{path.relative_to(ROOT)} missing {snippet!r}") + if missing: + raise SystemExit("FAILED: " + "; ".join(missing)) + print("OK: full day/night survival QA gate is tied to time, survival, HUD, and persistence coverage.") + + +if __name__ == "__main__": + main() diff --git a/Scripts/verify_stat_save_load_support.py b/Scripts/verify_stat_save_load_support.py index 78820e8..1f66529 100644 --- a/Scripts/verify_stat_save_load_support.py +++ b/Scripts/verify_stat_save_load_support.py @@ -36,13 +36,13 @@ EXPECTED = { "SavedPlayer.Inventory = InventoryComponent->Items;", "int32 UAgrarianPersistenceSubsystem::RestorePlayers", "SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);", - "InventoryComponent->Items = SavedPlayer->Inventory;", + "InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory);", "CapturePlayers(SaveGame);", + "RestoredPlayerCount = RestorePlayers(SaveGame);", "void UAgrarianPersistenceSubsystem::FindAgrarianPlayers", "FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId", ], "AgrarianGamePlayerController.cpp": [ - "const int32 RestoredPlayerCount = Persistence->RestorePlayers(SaveGame);", "Restored players: %d. Restored actors: %d.", ], "PersistenceDesignDocument.md": [