Skip to main content

Data & Persistence

Architecture

Lapis (DataStore) → Charm atoms → Charm-Sync → Client

Server-Side

  • Lapis handles DataStore access with session locking
  • Player data loaded into Charm atoms on join
  • Auto-synced to client via charm-sync producer

Client-Side

  • charm-sync consumer receives atom updates
  • Client reads atoms reactively via effect()

Player Data Schema

Defined in shared/data/types.ts:

interface PlayerData {
quirkymalStore: QuirkymalStore;
settings: PlayerSettings;
stats: PlayerStats;
}

QuirkymalStore

  • Selected quirkymal ID
  • Owned quirkymals list
  • Accessory items

PlayerStats

  • Runs attempted
  • Highest marker reached
  • Runs completed

Atom Hierarchy

World Atoms (world.store.ts)

Shared state about the game world:

  • dayNight — Current lighting mode
  • pathNodes — All path node BaseParts
  • pathNodeMap — Chunk index → node array
  • gameSeed — Session seed for deterministic generation
  • chunkPacing — Current pacing state
  • echoBoundaryIndex — Echo boundary for late joiners

Player Atoms (sync/atoms.ts)

Shared state about players:

  • dangerZonePlayers — Record of players in danger zones
  • safeZonePlayers — Record of players in safe zones
  • gamePhase"lobby" or "game"

Flattened Atoms

All atoms are flattened into a single map for charm-sync:

const atoms = flattenAtoms({
datastore, // "datastore/key"
world: worldAtoms, // "world/dayNight", "world/gameSeed", etc.
player: playerAtoms, // "player/gamePhase", etc.
});

Sync Pipeline

// Server (sync-server.ts)
const syncer = CharmSync.server({ atoms });
// Hydrates new clients, patches on change

// Client (sync-client.ts)
const syncer = CharmSync.client({ atoms });
// Applies server patches to local atoms

Validation

Player data is validated on load using @rbxts/t schemas defined in quirkymals/shared/schemas.ts.