# Agrarian Persistence Design Document ## Purpose This document defines Agrarian's persistence direction for the MVP foundation. It separates what the game saves, what remains external metadata, how save data should be versioned, and what must be deferred until later persistence systems are needed. The design goal is to preserve meaningful player and world progress without locking the project into brittle prototype data. ## Persistence Principles ### Save Meaningful State Persist things that change gameplay or player progress: - player identity; - survival stats; - inventory; - placed structures; - world time and weather state; - resource depletion where needed; - active tile/package version. Do not persist static terrain files, generated caches, editor-only assets, or pure presentation state. ### Version Everything That Can Outlive A Build Save data is a compatibility contract. Every persistent record should carry enough version/context to migrate or safely reject it later. Minimum version fields: - save format version; - game build/version; - active map/tile ID; - active tile package version; - record type version where needed. ### External Metadata Stays External Tile registry and terrain package metadata should remain external authoritative metadata. Save data should reference tile IDs/package versions, not copy entire tile registries into saves. ### Server Owns Writes Only the server writes authoritative persistence records. Clients display replicated state and may request actions, but they do not write authoritative save state directly. ## MVP Persistence Scope The MVP persistence scope is intentionally narrow: - player identity; - player survival snapshot; - player inventory snapshot; - placed campfire/shelter/basic build pieces; - resource depletion state where needed for shared gameplay; - world time; - weather state/seed/source metadata; - active Ground Zero tile ID; - active Ground Zero tile package version. Containers should be included once they become part of the playable loop. If containers are not in the first playable loop yet, they remain a near-term implementation item rather than a blocker for this design document. ## Player Identity MVP player identity can be simple. Acceptable early identity options: - local platform/user identifier; - deterministic test profile ID; - server-assigned temporary player ID; - future account ID once account services exist. Persistence should avoid using player display names as primary keys. Display names can change. Minimum player record: ```text PlayerId DisplayName LastKnownTileId LastKnownTransform SurvivalSnapshot CareHistorySnapshot InventorySnapshot UpdatedAt RecordVersion ``` ## Survival Snapshot Persist the survival state needed to resume a player: - health; - stamina; - hunger; - thirst; - body temperature; - injury severity; - sickness severity; - exhaustion; - alive/dead state if death persistence is active. - future corpse/backpack recovery records once death inventory loss is enabled. Survival rates and tuning values should not be duplicated into the save unless there is a specific compatibility reason. They belong in code/config/data assets. 0.1.J decision: the MVP death/respawn loop does not spawn a physical corpse or backpack actor yet. Respawn currently stabilizes the character at the Ground Zero MVP spawn/home location without inventory-drop recovery. When inventory loss is introduced, corpse/backpack records should be persistent, server-owned, time-limited or decay-aware, tied to the death event id/location, and recoverable through normal interaction rules. MVP implementation note: `UAgrarianPersistenceSubsystem::SaveCurrentWorld` captures live Agrarian player characters into `FAgrarianSavedPlayer`, including transform, survival snapshot, care history snapshot, and inventory stacks. `RestorePlayers` reapplies those records to matching live characters before or alongside world actor restore. The same save path captures authoritative world weather state from `AAgrarianGameState`: world hour, collapsed weather enum, mapped provider inputs, and `FAgrarianWeatherDebugSnapshot`. The debug snapshot includes weather source, provider timestamp, tile ID, tile center coordinate, provider weather code, and the final applied in-game weather state so weather-related save/load issues can be inspected per tile. `LoadCurrentWorld` is the unified MVP load entry point. It restores world weather/time state first, then restores matching player records and persistent world actors. Admin load commands should call this combined path so weather, players, and structures are rehydrated from the same save slot instead of partially restoring only actors or players. ## Care History Snapshot Reserve a long-term care history snapshot beside the immediate survival snapshot. The MVP records neutral normalized fields only; later lifecycle and aging systems will decide how aggressively those histories affect health, stamina, strength, endurance, disease resistance, injury risk, recovery, visual aging, and old-age decline. Reserved care history fields: - nutrition quality; - illness burden; - injury burden; - sleep quality; - shelter quality; - stress burden; - workload burden; - treatment quality. ## Inventory Snapshot Persist inventory as item definition IDs and stack counts. Do persist: - item ID; - stack count; - durability/condition when added; - container/location ID when containers exist. Do not persist full item definition data. Item definitions belong in Data Assets and are loaded by ID. ## World State MVP world state should include: - world/gameplay time; - weather state; - weather seed or source timestamp when available; - active tile ID; - active tile package version; - save timestamp; - save format version. World time uses the MVP calendar target: ```text 4 real hours = 1 in-game day ``` Day/night presentation can later use real-region solar metadata, but the save should persist the gameplay calendar state and enough tile context to recreate presentation. ## Structure And Placement State Placed gameplay structures should persist when they are part of the survival loop. Minimum placed object record: ```text ObjectId ObjectTypeId TileId Transform OwnerPlayerId HealthOrCondition CustomState CreatedAt UpdatedAt RecordVersion ``` For MVP, this includes: - campfire; - primitive shelter; - basic build pieces once available. Future structure persistence should support decay, ownership, permissions, settlement membership, repair state, and destruction history. ## Resource Depletion State Not every resource needs persistent depletion in the MVP. Persist depletion when: - multiple players can observe the changed state; - the depleted state affects survival; - immediate respawn would undermine the loop; - the actor is intentionally part of a shared world memory test. Avoid persisting every foliage or minor gatherable actor until performance and world-density rules are clear. Minimum resource state: ```text ResourceId ResourceTypeId TileId RemainingAmount DepletedAt RespawnAt RecordVersion ``` ## Tile Metadata In Save Data Save data should reference external tile metadata, not duplicate it. Persist in save: - active tile ID; - active tile package version; - tile registry schema version; - tile package checksum or manifest hash if needed for validation. Keep external: - full tile registry; - terrain metadata; - heightmap metadata; - landform analysis; - water/shoreline analysis; - neighbor edge verification; - tile package files. Reason: - terrain packages may improve over time; - tile registry can grow independently; - save files should stay small and migrate safely; - player/world state should not corrupt when tile metadata updates. ## Containers Containers are a near-term persistence item once storage gameplay exists. Minimum container record: ```text ContainerId ContainerTypeId TileId Transform OwnerPlayerId InventorySnapshot LockOrPermissionState RecordVersion ``` Until containers are implemented, inventory can remain player-carried and placed structure state can remain separate. ## Save Timing MVP save timing should include: - manual admin save command; - load on server start; - periodic server-side save interval once server state is changing regularly; - explicit save before planned server shutdown. Recommended initial interval: ```text every 5 minutes during active play ``` The interval should be tunable. It should not block gameplay on slow disk I/O. ## Load On Server Start Server startup should: 1. Load save metadata. 2. Validate save format version. 3. Validate active tile ID and package version. 4. Load world state. 5. Load placed structures/resource state. 6. Load known player records. 7. Spawn or restore runtime actors. 8. Log skipped or migrated records. If save validation fails, the server should fail clearly or start a new world only when explicitly configured to do so. ## Migration Strategy Persistence must assume data formats will change. Rules: - never silently reinterpret old records as new formats; - add migration functions when changing saved record shape; - keep unknown fields harmless where possible; - log migrations; - reject unsupported future save versions; - back up saves before destructive migrations. MVP can use simple file saves, but the data shape should still include versions so future database migration is possible. ## Storage Backend MVP storage can be file-based while the system is small. Acceptable MVP options: - Unreal `USaveGame` records; - JSON records for inspectability; - hybrid approach where gameplay uses `USaveGame` and tile metadata remains JSON/external. Long-term candidates: - SQLite for local/server structured state; - PostgreSQL for persistent multiplayer services; - object storage/CDN for tile packages; - append-only event logs for economy/governance where auditability matters. Do not choose the long-term database before the MVP proves the shape of the state that needs to persist. ## Backup And Recovery Persistence is not a backup by itself. MVP operations should keep: - repository backup; - VM backup; - save data backup; - tile package backup; - tile registry backup. Before changing save formats, take a manual backup of current save data. ## Security Persistent data should never store secrets. Do not store: - passwords; - API tokens; - private keys; - admin reset tokens; - mail credentials; - DigitalOcean tokens. Player records should store stable IDs and gameplay state, not sensitive account credentials. For the MVP `USaveGame` path, each saved player keeps both a backwards-compatible `PlayerId` string and an `FAgrarianSavedPlayerIdentity` block. The stable ID prefers the replicated `APlayerState` unique network ID when one exists, then falls back to player name, then pawn name for local prototype sessions. The identity block also records the display player name, raw network ID, whether the network ID was used, and the last known pawn name. It deliberately does not store passwords, tokens, emails, or platform credentials. Player stats are stored through `FAgrarianSavedPlayer::Survival`, which captures the replicated `FAgrarianSurvivalSnapshot`. The MVP stat save includes health, stamina, exhaustion, hunger, thirst, body temperature, generic injury, bleeding, sprain, sickness, death state, and death reason. Loading applies the snapshot through `UAgrarianSurvivalComponent::ApplySavedState`, keeping clamping and replicated change notifications inside the survival component. Long-term care history placeholders are stored through `FAgrarianSavedPlayer::CareHistory`. The saved `FAgrarianCareHistorySnapshot` reserves nutrition, illness, injury, sleep, shelter, stress, workload, and treatment quality data for later generational and health systems. Version 0.1.M only persists and restores those placeholders; it does not apply aging, lifespan, inheritance, or generational outcome gameplay. Player inventory is stored through `FAgrarianSavedPlayer::Inventory`, copied from `UAgrarianInventoryComponent::Items`, and restored through `UAgrarianInventoryComponent::RestoreSavedItems`. Restore recomputes derived values and broadcasts inventory changes so HUD/debug listeners see loaded item state. World time is stored in `UAgrarianSaveGame::WorldHours`. The persistence subsystem captures it from `AAgrarianGameState::WorldHours` during world save and restores it only on authority during world load. Weather state is stored as the fallback `UAgrarianSaveGame::Weather` enum plus `WeatherInputs` and `WeatherDebug` snapshots. When provider data exists, load reapplies the mapped weather inputs; otherwise it restores the saved enum weather state. This gives the MVP a deterministic fallback while preserving the latest real/provider-mapped tile weather payload when one was available. ## Testing Gates Minimum persistence smoke test: 1. Start server. 2. Join as a player. 3. Change survival/inventory state. 4. Place a campfire or primitive shelter. 5. Deplete one resource node if resource persistence is enabled. 6. Save manually. 7. Stop server. 8. Start server. 9. Confirm player/world state restored as expected. Minimum migration test: 1. Load a previous save format fixture. 2. Run migration or compatibility load. 3. Confirm unsupported data is rejected or logged clearly. 4. Confirm supported data survives. Minimum tile validation test: 1. Save active Ground Zero tile ID/package version. 2. Restart server. 3. Confirm the server validates the active package version. 4. Confirm clients can still download the required package from the tile endpoint. ## Current Roadmap Decisions This document defines: - MVP persistence scope; - save data vs external tile registry boundary; - player identity save direction; - player stats save direction; - player inventory save direction; - resource depletion persistence rule; - world time save direction; - weather seed/state save direction; - container persistence direction; - server-side save interval direction; - load-on-server-start direction; - initial tile registry persistence direction. Implementation work remains tracked separately in the roadmap. The MVP resource-node implementation stores depletion as existing map/tile actor state. Each `AAgrarianResourceNode` may define a stable `PersistenceNodeId`; otherwise the actor name is used as a fallback. Save files capture `RemainingHarvests` for resource nodes present in the loaded world and restore only matching existing nodes. This deliberately avoids spawning resource nodes from saves, keeping tile-authored resources owned by tile content while letting persistence remember depletion for active tiles. Resource depletion is stored in `FAgrarianSavedResourceNode` records with stable `ResourceNodeId`, remaining harvest count, and the MVP respawn flag. The persistence subsystem captures active loaded resource nodes during world save and applies depletion only to matching map-authored nodes during load. ## Open Questions - Should the first playable MVP use `USaveGame`, JSON, or a hybrid save backend? - What is the first stable `SaveFormatVersion` value? - Should disconnected players remain in world physically or be removed? - How much inventory should death/respawn preserve? - When do saves move from file-based records to database-backed records? - How should family/generation records attach to player identity later?