#!/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()