This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
AgrarianGameArchive/Docs/PersistenceDesignDocument.md
T

512 lines
15 KiB
Markdown

# 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?