Search is available after the production docs build.

Browse Docs
DocsDevelopersData Storage

Developers

Data Storage

Persistent player and world data ownership, migration, and save compatibility guidance.

Role

DataCore owns persistent player, world, and team data for ECHO systems. Modules need to know who owns data, how it is keyed, how it migrates, and how it is synchronized.

Data storage is a platform concern because bad persistence breaks progression, support, repair, and runtime portability. Use stable IDs and versioned records instead of raw runtime objects.

Descriptor Requirements

Declare DataCore and data permissions when a module persists shared state:

{
  "schema": "echo.mod.v1",
  "id": "echodatacore",
  "provides": ["echo.data"],
  "consumes": ["echo.core", "echo.net"],
  "permissions": ["data.persistence"],
  "apiStability": "stable"
}

A consuming module should list echodatacore as requires when it cannot function without persistence, or optional when it can run with read-only or stateless fallback behavior.

Ownership

Every persistent field should have an owner. Ownership answers:

  • Which module writes this data?
  • Which systems may read it?
  • What happens if the module is missing?
  • How is the data migrated?
  • Can the data be rebuilt from other state?
  • Which UI surfaces expose it?
  • Which network actions can change it?

If a module cannot answer those questions, the data model is not ready.

Current DataCore Model

Current first-party DataCore behavior uses:

  • Data scopes: player, world, and team.
  • Stable key IDs.
  • Value kinds such as flag, counter, enum, and record.
  • Metadata for titles, descriptions, defaults, and visibility.
  • Dirty tracking and revision-based sync.
  • Migration markers under the DataCore root.
  • Diagnostics for conflicts, stale datapack metadata, and sync pressure.

The internal first-party implementation lives in ECHO-Modules/addons/echodatacore. Public docs should describe the contract and metadata expectations; do not copy private storage internals into SDK samples.

Player Data

Player data can include discoveries, mission progress, unlocks, scanned targets, Index entries, route state, faction reputation, and tutorial state.

Keep player data scoped and readable. A single unstructured blob is hard to migrate, debug, or validate.

Example player record:

{
  "owner": "echorelictech",
  "scope": "player",
  "key": "echorelictech:relic_scan_state",
  "schemaVersion": 2,
  "valueKind": "record",
  "defaultValue": {
    "discoveredRelics": [],
    "scannedTargets": {},
    "unlockedIndexEntries": []
  }
}

The version marker matters. Even small modules should assume their data may need to evolve.

World Data

World data can include region state, hazard maps, discovered locations, relay networks, route unlocks, global events, and anomaly conditions.

World data should avoid assuming that every chunk, entity, or marker is loaded at all times. A HoloMap overlay may need to show a route even when the underlying region is not loaded.

Example world record:

{
  "owner": "echoworldcore",
  "scope": "world",
  "key": "echoworldcore:active_hazards",
  "schemaVersion": 1,
  "valueKind": "record",
  "defaultValue": {
    "regions": [],
    "lastUpdatedTick": 0
  }
}

Prefer stable IDs and region references over direct runtime object references.

Migration

Any versioned data needs a migration story. Migration notes belong in module docs and release notes before the module ships.

A migration entry should document:

  • Old schema version.
  • New schema version.
  • Owner module.
  • Data scope.
  • Fields copied directly.
  • Fields renamed.
  • Fields dropped.
  • Fallback when data cannot be migrated.
  • Whether the migration is idempotent.

First-party DataCore records migration markers so repeated login or world-load migration can run safely without changing data again.

Networking And Sync

Data writes that affect UI should synchronize through NetCore rather than direct UI mutation.

Use small updates:

  • Scope and owner ID.
  • Revision.
  • Changed keys.
  • Full snapshot flag only when needed.
  • Redacted or clipped payloads when data is too large.

Terminal, Index, Lens, and HoloMap should read through services or synchronized metadata, not raw persistent roots.

Storage Checklist

  • Every saved field has an owner.
  • Every saved object has a schema version.
  • Runtime references are stored as stable IDs when possible.
  • Missing modules have a defined fallback behavior.
  • UI surfaces read through services, not raw storage.
  • Network actions validate before mutating state.
  • Migrations are documented before release.
  • Diagnostics can explain duplicate keys, metadata conflicts, and sync pressure.

Common Mistakes

  • Storing direct runtime objects in long-lived ECHO state.
  • Letting UI modules mutate storage directly.
  • Combining player, world, and team data in one unstructured blob.
  • Adding new fields without migration notes.
  • Treating missing data as impossible.
  • Synchronizing full state when a key and revision would work.

Related Docs

Next Step

After defining ownership, document which services expose the data and which UI surfaces display it. Start with Service Contracts, then connect player-facing state through UI Integration.