Add reconnect state retention QA gate
This commit is contained in:
@@ -862,7 +862,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
- [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.
|
||||
- [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.
|
||||
- [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.
|
||||
- [ ] No critical log spam during 30-minute test.
|
||||
- [ ] Clean up Unreal API deprecation warnings from packaged builds, starting
|
||||
|
||||
@@ -179,3 +179,28 @@ Required evidence:
|
||||
|
||||
This gate is gameplay/server-relevant and should be covered by the next server
|
||||
package deployment.
|
||||
|
||||
## Reconnect State Retention
|
||||
|
||||
The reconnect state retention gate proves a player can disconnect, reconnect,
|
||||
and recover the MVP state that matters for a continuing survival session instead
|
||||
of starting over from a blank pawn.
|
||||
|
||||
Required evidence:
|
||||
|
||||
- `AAgrarianGameGameMode::Logout` captures a player snapshot before the pawn is
|
||||
released.
|
||||
- `AAgrarianGameGameMode::RestartPlayer` restores a matching snapshot after the
|
||||
normal MVP spawn path.
|
||||
- `UAgrarianPersistenceSubsystem::SavePlayerSnapshot` and
|
||||
`RestorePlayerSnapshot` preserve transform, safe player identity, survival,
|
||||
care history, and inventory.
|
||||
- Inventory restore uses `RestoreSavedItems` so derived carry weight and
|
||||
inventory listeners refresh after reconnect.
|
||||
- If no matching snapshot exists, the player falls back to the normal MVP spawn
|
||||
point instead of failing the join.
|
||||
- The manual two-client test records reconnect evidence alongside world
|
||||
time/weather consistency.
|
||||
|
||||
This gate is server-relevant and must be rechecked after multiplayer package
|
||||
deployment.
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify the MVP reconnect state-retention 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"
|
||||
NETWORKING = ROOT / "Docs" / "MultiplayerNetworkingDesign.md"
|
||||
GAME_MODE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameGameMode.h"
|
||||
GAME_MODE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameGameMode.cpp"
|
||||
PERSISTENCE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.h"
|
||||
PERSISTENCE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp"
|
||||
SAVE_GAME_H = ROOT / "Source" / "AgrarianGame" / "AgrarianSaveGame.h"
|
||||
INVENTORY_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.cpp"
|
||||
DISCONNECT_VERIFY = ROOT / "Scripts" / "verify_disconnect_reconnect_handling.py"
|
||||
IDENTITY_VERIFY = ROOT / "Scripts" / "verify_player_identity_persistence.py"
|
||||
STATS_VERIFY = ROOT / "Scripts" / "verify_player_stats_persistence.py"
|
||||
INVENTORY_VERIFY = ROOT / "Scripts" / "verify_inventory_persistence.py"
|
||||
TWO_CLIENT_VERIFY = ROOT / "Scripts" / "verify_two_client_connection_gate.py"
|
||||
|
||||
REQUIRED = {
|
||||
QA_DOC: [
|
||||
"## Reconnect State Retention",
|
||||
"Logout",
|
||||
"RestartPlayer",
|
||||
"transform, safe player identity, survival",
|
||||
"RestoreSavedItems",
|
||||
],
|
||||
NETWORKING: [
|
||||
"## Disconnect And Reconnect",
|
||||
"player reconnect snapshot",
|
||||
"preserves transform, survival, care history, and",
|
||||
"inventory through `UAgrarianPersistenceSubsystem::SavePlayerSnapshot`",
|
||||
"normal MVP spawn point",
|
||||
],
|
||||
GAME_MODE_H: [
|
||||
"virtual void RestartPlayer(AController* NewPlayer) override;",
|
||||
"virtual void Logout(AController* Exiting) override;",
|
||||
],
|
||||
GAME_MODE_CPP: [
|
||||
"Super::RestartPlayer(NewPlayer)",
|
||||
"Persistence->RestorePlayerSnapshot(AgrarianCharacter)",
|
||||
"Persistence->SavePlayerSnapshot(AgrarianCharacter)",
|
||||
"Super::Logout(Exiting)",
|
||||
],
|
||||
PERSISTENCE_H: [
|
||||
"bool SavePlayerSnapshot(const AAgrarianGameCharacter* Character) const;",
|
||||
"bool RestorePlayerSnapshot(AAgrarianGameCharacter* Character) const;",
|
||||
],
|
||||
PERSISTENCE_CPP: [
|
||||
"SavedPlayer.Transform = Character->GetActorTransform();",
|
||||
"SavedPlayer.Survival = SurvivalComponent->Survival;",
|
||||
"SavedPlayer.CareHistory = SurvivalComponent->CareHistory;",
|
||||
"SavedPlayer.Inventory = InventoryComponent->Items;",
|
||||
"Character->SetActorTransform(SavedPlayer->Transform",
|
||||
"InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory);",
|
||||
"return UniqueId->ToString();",
|
||||
],
|
||||
SAVE_GAME_H: [
|
||||
"struct FAgrarianSavedPlayer",
|
||||
"FAgrarianSavedPlayerIdentity Identity;",
|
||||
"FTransform Transform;",
|
||||
"FAgrarianSurvivalSnapshot Survival;",
|
||||
"TArray<FAgrarianItemStack> Inventory;",
|
||||
],
|
||||
INVENTORY_CPP: [
|
||||
"UAgrarianInventoryComponent::RestoreSavedItems",
|
||||
"BroadcastInventoryChanged()",
|
||||
],
|
||||
DISCONNECT_VERIFY: ["PASS: MVP disconnect/reconnect snapshot handling"],
|
||||
IDENTITY_VERIFY: ["player identity persistence"],
|
||||
STATS_VERIFY: ["player stats persist"],
|
||||
INVENTORY_VERIFY: ["inventory persistence"],
|
||||
TWO_CLIENT_VERIFY: ["Two-Client Connection"],
|
||||
ROADMAP: [
|
||||
"[x] Can reconnect and retain state.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
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: reconnect state-retention QA gate is tied to logout/restart snapshots and persistence coverage.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user