Add server restart shelter persistence QA gate
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<UAgrarianPersistentActorComponent>(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()
|
||||
Reference in New Issue
Block a user