From d82d6bcc411621e2c4308dde50a2cc42d64b2317 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 19 May 2026 14:07:27 -0700 Subject: [PATCH] Add server restart shelter persistence QA gate --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/QA/MvpQaGates.md | 25 +++++ ...ver_restart_shelter_persistence_qa_gate.py | 91 +++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_server_restart_shelter_persistence_qa_gate.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 0ab5d02..32b3540 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -863,7 +863,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [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. - [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. - [x] Can reconnect and retain state. Added a reconnect state-retention QA gate tied to logout/restart player snapshots, safe player identity, transform, survival, care history, inventory restore, normal-spawn fallback behavior, and the two-client manual reconnect evidence path. -- [ ] Can restart server and retain placed shelter. +- [x] Can restart server and retain placed shelter. Added a server-restart shelter persistence QA gate tied to `primitive_shelter` persistent actor state, world actor save/load, game-mode class registration, load-on-server-start behavior, shelter weather protection, and a release smoke requirement to place, save, restart, and confirm the shelter transform remains. - [ ] No critical log spam during 30-minute test. - [ ] Clean up Unreal API deprecation warnings from packaged builds, starting with direct `NetCullDistanceSquared` access on replicated world actors before diff --git a/Docs/QA/MvpQaGates.md b/Docs/QA/MvpQaGates.md index 378728f..6f9f0c8 100644 --- a/Docs/QA/MvpQaGates.md +++ b/Docs/QA/MvpQaGates.md @@ -204,3 +204,28 @@ Required evidence: This gate is server-relevant and must be rechecked after multiplayer package deployment. + +## Server Restart Shelter Persistence + +The server restart shelter persistence gate proves a placed primitive shelter is +not merely a runtime actor. It must be captured into the world save, restored by +the server on startup, and remain available for weather protection after a +restart. + +Required evidence: + +- `AAgrarianShelterActor` owns a persistent actor component with + `ActorTypeId = primitive_shelter`. +- The game mode registers `primitive_shelter` with the persistence subsystem + before loading current world state. +- `SaveCurrentWorld` captures world actors and `LoadCurrentWorld` restores + registered world actors. +- Server startup uses `bLoadWorldOnServerStart`, checks `DoesSaveExist`, and + calls `LoadCurrentWorld` without clearing existing map actors. +- Shelter weather-protection verification remains tied to the restored shelter + actor. +- A release smoke run should place a shelter, save, restart the server, and + confirm the shelter remains at the same transform. + +This gate is server-relevant and must be rechecked after the final 0.1.Q server +package/deploy if server code or package contents changed. diff --git a/Scripts/verify_server_restart_shelter_persistence_qa_gate.py b/Scripts/verify_server_restart_shelter_persistence_qa_gate.py new file mode 100644 index 0000000..e9bc583 --- /dev/null +++ b/Scripts/verify_server_restart_shelter_persistence_qa_gate.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Verify the MVP server-restart shelter persistence 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" +PERSISTENCE_DOC = ROOT / "Docs" / "PersistenceDesignDocument.md" +GAME_MODE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameGameMode.h" +GAME_MODE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameGameMode.cpp" +PERSISTENCE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp" +SHELTER_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianShelterActor.cpp" +PERSISTENT_COMPONENT_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistentActorComponent.cpp" +EDITOR_AUTOMATION_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianEditorAutomationLibrary.cpp" +LOAD_ON_START_VERIFY = ROOT / "Scripts" / "verify_load_on_server_start.py" +SHELTER_VERIFY = ROOT / "Scripts" / "verify_shelter_weather_protection.py" +CRAFT_SHELTER_VERIFY = ROOT / "Scripts" / "verify_craft_shelter_qa_gate.py" + +REQUIRED = { + QA_DOC: [ + "## Server Restart Shelter Persistence", + "ActorTypeId = primitive_shelter", + "bLoadWorldOnServerStart", + "SaveCurrentWorld", + "LoadCurrentWorld", + ], + PERSISTENCE_DOC: [ + "`bLoadWorldOnServerStart`", + "`DoesSaveExist`", + "`LoadCurrentWorld` without clearing existing map", + ], + GAME_MODE_H: [ + "bool bLoadWorldOnServerStart = true;", + "void RegisterPersistentActorClasses", + "void LoadWorldOnServerStart();", + ], + GAME_MODE_CPP: [ + "RegisterPersistentActorClasses(Persistence);", + "Persistence->RegisterWorldActorClass(TEXT(\"primitive_shelter\"), AAgrarianShelterActor::StaticClass());", + "Persistence->DoesSaveExist()", + "constexpr bool bClearExistingActors = false;", + "Persistence->LoadCurrentWorld(RestoredPlayerCount, RestoredActorCount, bClearExistingActors);", + "Persistence->SaveCurrentWorld()", + ], + PERSISTENCE_CPP: [ + "int32 UAgrarianPersistenceSubsystem::CaptureWorldActors", + "int32 UAgrarianPersistenceSubsystem::RestoreWorldActors", + "WorldActorClassRegistry.Find(SavedActor.ActorTypeId)", + "Component->ApplySaveState(SavedActor);", + "CaptureWorldActors(SaveGame);", + "RestoreWorldActors(SaveGame, bClearExistingActors);", + ], + SHELTER_CPP: [ + "PersistentActorComponent = CreateDefaultSubobject(TEXT(\"PersistentActorComponent\"));", + "PersistentActorComponent->ActorTypeId = TEXT(\"primitive_shelter\")", + "ProtectionVolume->SetBoxExtent", + ], + PERSISTENT_COMPONENT_CPP: [ + "SavedActor.ActorTypeId = ActorTypeId;", + "SavedActor.Transform = GetOwner()->GetActorTransform();", + "GetOwner()->SetActorTransform(SavedActor.Transform", + ], + EDITOR_AUTOMATION_CPP: [ + "crafted and placed primitive_shelter", + "saved %d persistent actor(s), restored %d actor(s)", + ], + LOAD_ON_START_VERIFY: ["PASS: authoritative server startup load"], + SHELTER_VERIFY: ["shelter weather protection"], + CRAFT_SHELTER_VERIFY: ["## Craft Shelter"], + ROADMAP: [ + "[x] Can restart server and retain placed shelter.", + ], +} + + +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: server-restart shelter persistence QA gate is tied to world actor persistence and load-on-start coverage.") + + +if __name__ == "__main__": + main()