17 KiB
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:
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:
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:
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:
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:
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:
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:
- Load save metadata.
- Validate save format version.
- Validate active tile ID and package version.
- Load world state.
- Load placed structures/resource state.
- Load known player records.
- Spawn or restore runtime actors.
- 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
USaveGamerecords; - JSON records for inspectability;
- hybrid approach where gameplay uses
USaveGameand 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.
Container persistence reserves FAgrarianSavedContainer records with stable
container ID, container type ID, transform, item stacks, and owner player ID.
Version 0.1.M does not yet have a placed container actor to capture; the current
simple_container remains a craftable inventory item and is covered by player
inventory persistence. The saved container array is intentionally present now
so the first placed-container actor can use the same save format without a
schema break.
Ground Zero tile registry persistence is represented by
FAgrarianSavedTileRegistryState on UAgrarianSaveGame. The MVP defaults point
to active tile gz_us_ca_pacifica_utm10n_e544_n4160, registry source
Data/Tiles/ground_zero_tiles.json, registry schema version 1, generation
version 1, and package version 0. This records the active tile baseline
inside the save file while the authoritative tile registry remains the external
JSON registry.
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.
The server-side autosave interval lives on AAgrarianGameGameMode as
ServerAutoSaveIntervalSeconds, defaulting to five minutes. On authority,
BeginPlay starts a repeating timer that calls RunServerAutoSave, which uses
UAgrarianPersistenceSubsystem::SaveCurrentWorld. Setting the interval to zero
disables the MVP autosave timer.
Load-on-server-start also lives on AAgrarianGameGameMode through
bLoadWorldOnServerStart, enabled by default. On authoritative BeginPlay, the
GameMode registers the current MVP persistent actor classes, checks
DoesSaveExist, and calls LoadCurrentWorld without clearing existing map
actors so map-authored resources and startup content remain owned by the map.
Testing Gates
Minimum persistence smoke test:
- Start server.
- Join as a player.
- Change survival/inventory state.
- Place a campfire or primitive shelter.
- Deplete one resource node if resource persistence is enabled.
- Save manually.
- Stop server.
- Start server.
- Confirm player/world state restored as expected.
Minimum migration test:
- Load a previous save format fixture.
- Run migration or compatibility load.
- Confirm unsupported data is rejected or logged clearly.
- Confirm supported data survives.
Minimum tile validation test:
- Save active Ground Zero tile ID/package version.
- Restart server.
- Confirm the server validates the active package version.
- 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
SaveFormatVersionvalue? - 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?