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-syncproducer
Client-Side
charm-syncconsumer 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 modepathNodes— All path node BasePartspathNodeMap— Chunk index → node arraygameSeed— Session seed for deterministic generationchunkPacing— Current pacing stateechoBoundaryIndex— Echo boundary for late joiners
Player Atoms (sync/atoms.ts)
Shared state about players:
dangerZonePlayers— Record of players in danger zonessafeZonePlayers— Record of players in safe zonesgamePhase—"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.