From 428918bc75007a03adf94493058870e5f99aea91 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 14:02:55 -0700 Subject: [PATCH] Add survival pressure death QA gate --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/QA/MvpQaGates.md | 25 ++++++ Scripts/verify_exhaustion_stat.py | 2 +- Scripts/verify_sickness_placeholder.py | 2 +- .../verify_survival_pressure_death_qa_gate.py | 83 +++++++++++++++++++ 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 Scripts/verify_survival_pressure_death_qa_gate.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index cfcd46f..b2f55e9 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -861,7 +861,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [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. - [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. +- [x] Can die from survival pressure. Added a survival-pressure death QA gate requiring starvation, dehydration, cold exposure, sickness, and bleeding to reduce health on server authority, trigger `UpdateDeathState`, replicate `bIsDead` and `LastDeathReason`, show death/respawn UI feedback, support server respawn, and remain covered by player stat persistence. - [ ] Can reconnect and retain state. - [ ] Can restart server and retain placed shelter. - [ ] No critical log spam during 30-minute test. diff --git a/Docs/QA/MvpQaGates.md b/Docs/QA/MvpQaGates.md index 7657c06..93c0f29 100644 --- a/Docs/QA/MvpQaGates.md +++ b/Docs/QA/MvpQaGates.md @@ -154,3 +154,28 @@ Required evidence: This gate is gameplay/server-relevant and should be covered by the next server package deployment. + +## Survival Pressure Death + +The survival-pressure death gate proves the MVP does not merely display hunger, +thirst, exposure, illness, or injury as cosmetic stats. Sustained pressure must +be able to reduce health, mark the character dead, replicate the death state, +and expose a clear respawn path. + +Required evidence: + +- Starvation, dehydration, cold exposure, sickness, and bleeding can reduce + `Survival.Health` on server authority. +- `ClampSurvival` calls `UpdateDeathState`, clamps health to zero, clears + stamina, sets `bIsDead`, and records `LastDeathReason`. +- The replicated survival snapshot includes the death state and last death + reason. +- The critical survival HUD and death/respawn panel display the dead state and + cause. +- The player controller exposes the MVP respawn path and revives the survival + component on the server. +- Death/respawn behavior remains persistence-safe through the player stat + save/load path. + +This gate is gameplay/server-relevant and should be covered by the next server +package deployment. diff --git a/Scripts/verify_exhaustion_stat.py b/Scripts/verify_exhaustion_stat.py index 043d87b..7bfd499 100644 --- a/Scripts/verify_exhaustion_stat.py +++ b/Scripts/verify_exhaustion_stat.py @@ -33,7 +33,7 @@ EXPECTED = { "AgrarianGameCharacter.cpp": [ "SurvivalComponent->Survival.Exhaustion < 85.0f", "const float ExhaustionMultiplier = FMath::GetMappedRangeValueClamped", - "HungerMultiplier * ThirstMultiplier * InjuryMultiplier * ExhaustionMultiplier", + "HungerMultiplier * ThirstMultiplier * InjuryMultiplier * SprainMultiplier * SicknessMultiplier * ExhaustionMultiplier", ], "AgrarianDebugHUD.cpp": [ "Exhaust:", diff --git a/Scripts/verify_sickness_placeholder.py b/Scripts/verify_sickness_placeholder.py index 41b8306..73eee45 100644 --- a/Scripts/verify_sickness_placeholder.py +++ b/Scripts/verify_sickness_placeholder.py @@ -31,7 +31,7 @@ EXPECTED = { "AgrarianGameCharacter.cpp": [ "const float SicknessMultiplier = FMath::GetMappedRangeValueClamped", "Survival.SicknessSeverity", - "InjuryMultiplier * SicknessMultiplier * ExhaustionMultiplier", + "InjuryMultiplier * SprainMultiplier * SicknessMultiplier * ExhaustionMultiplier", ], "AgrarianDebugHUD.cpp": ["Sick:", "Survival.SicknessSeverity"], "AgrarianGamePlayerController.cpp": [ diff --git a/Scripts/verify_survival_pressure_death_qa_gate.py b/Scripts/verify_survival_pressure_death_qa_gate.py new file mode 100644 index 0000000..498e9de --- /dev/null +++ b/Scripts/verify_survival_pressure_death_qa_gate.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Verify the MVP survival-pressure death 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" +TYPES_H = ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h" +SURVIVAL_H = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h" +SURVIVAL_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp" +HUD_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp" +CONTROLLER_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp" +DEATH_VERIFY = ROOT / "Scripts" / "verify_death_state.py" +REPLICATED_DEATH_VERIFY = ROOT / "Scripts" / "verify_replicated_death_feedback.py" +RESPAWN_VERIFY = ROOT / "Scripts" / "verify_mvp_death_respawn_ui.py" +STAT_SAVE_VERIFY = ROOT / "Scripts" / "verify_stat_save_load_support.py" + +REQUIRED = { + QA_DOC: [ + "## Survival Pressure Death", + "Starvation, dehydration, cold exposure, sickness, and bleeding", + "UpdateDeathState", + "LastDeathReason", + "death/respawn panel", + ], + TYPES_H: [ + "bool bIsDead = false;", + "FName LastDeathReason = NAME_None;", + ], + SURVIVAL_H: [ + "float StarvationDamagePerMinute = 3.0f;", + "float DehydrationDamagePerMinute = 5.0f;", + "float ColdDamagePerMinute = 4.0f;", + "float SicknessDamagePerMinute = 1.5f;", + "float BleedingDamagePerMinute = 2.0f;", + "void UpdateDeathState();", + ], + SURVIVAL_CPP: [ + "Survival.Health -= StarvationDamagePerMinute * Minutes;", + "Survival.Health -= DehydrationDamagePerMinute * Minutes;", + "Survival.Health -= ColdDamagePerMinute * Minutes", + "Survival.Health -= SicknessDamagePerMinute * (Survival.SicknessSeverity / 100.0f) * Minutes;", + "Survival.Health -= BleedingDamagePerMinute * BleedingRatio * Minutes;", + "UpdateDeathState();", + "Survival.bIsDead = true;", + "Survival.LastDeathReason = FName(TEXT(\"health_depleted\"));", + "OnDeathStateChanged.Broadcast(Survival.bIsDead, Survival.LastDeathReason);", + ], + HUD_CPP: [ + "YOU DID NOT SURVIVE", + "Cause: %s", + "State DEAD", + ], + CONTROLLER_CPP: [ + "ServerAgrarianRespawn_Implementation", + "SurvivalComponent->Revive", + ], + DEATH_VERIFY: ["PASS: death state is present."], + REPLICATED_DEATH_VERIFY: ["replicated death feedback"], + RESPAWN_VERIFY: ["MVP death/respawn UI verification"], + STAT_SAVE_VERIFY: ["SavedPlayer.Survival", "ApplySavedState"], + ROADMAP: [ + "[x] Can die from survival pressure.", + ], +} + + +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: survival-pressure death QA gate is tied to damage, death, HUD, respawn, and persistence coverage.") + + +if __name__ == "__main__": + main()