Steal An EggDocumentation← Portfolio

#Data & Persistence

#One profile per player

DataService owns everything that survives a session. Profiles live in the SAE_Profiles_v1 DataStore, keyed per user id, and are cached in memory while the player is in the server.

SettingValue
DataStore nameSAE_Profiles_v1
Config.PROFILE_SCHEMA_VERSION2
Load retries / delay4 attempts, 2 seconds apart
Autosave intervalConfig.Economy.SAVE_INTERVAL = 60 s
Save on shutdownDataService.bindToClose()
Offline incomeConfig.Economy.OFFLINE_INCOME = false

The bootstrap loads a profile before anything else happens: PlayerStateService.initRuntime → DataService.load → plot assignment → speed, treadmill, passes, friend bonus, tutorial, growth and pet rebuild. A player whose profile fails to load is warned and skipped rather than half-initialised.

#Profile shape

DataService.defaultProfile() defines a fresh profile:

FieldMeaning
schemaVersionMigration marker, currently 2
createdAt, lastSeenUnix timestamps
cash, speedCurrencies (a new profile starts with 10 Speed)
unlockedZones{ Forest = true } and whatever the Speed requirement unlocked
treadmillId, ownedTreadmillsEquipped treadmill and the owned set
activeTrail, ownedTrailsEquipped trail and the owned set
batId, batsEquipped bat and the owned set
penLevelIndex into Config.Pen.Levels
petsInventory: uid → pet record
placedPetsuid → placement, the set that earns income
growinguid → grow entry (petId, weightMul, sizeId, pos, startedAt, finishAt)
eggItemsEggs stored as inventory items
index, indexClaimedDiscovered pets and claimed reward keys
settingsmusic, sfx, showOwnPets, showOtherPets, slowMode
statstotalEarned, totalStolen, totalHatched, totalFused, bestIncome, playTime
eventsEvent progress: riftTokens, mutationShards, sakuraCrystals, bossMastery, freeGiftClaimed, incubatorCharge, boost timestamps and more
cooldownsTimed gates such as growAllFree
purchasesRecord of processed receipts

#Writes, saves and lifetime

  • Services mutate the cached profile and call DataService.markDirty(player).
  • DataService.stripRuntime removes runtime-only helpers before the record is written, so a save never contains live Instances or transient flags.
  • The autosave loop flushes dirty profiles every 60 seconds; bindToClose flushes again on shutdown.
  • DataService.release(player) drops the cache entry when the player leaves.

#Migrations

Two functions keep old profiles valid:

  • migrate(profile) upgrades a profile whose schemaVersion is behind the current one.
  • reconcile(profile) fills in fields that did not exist when the profile was created — this is what keeps the growing events and settings tables consistent across updates.

When you add a field, add it to defaultProfile and to reconcile, otherwise old profiles will be missing it at runtime.

#Studio behaviour

In Studio, DataService.isPersistent() is false unless the place has API access enabled (Game Settings → Security → Enable Studio Access to API Services). Without it, profiles are kept in memory only: everything works, nothing is saved, and the admin datastore command reports the problem. DataService.selfTest() performs a real read/write check and is what that command calls.

#Extras worth knowing

  • DataService.ProfileLoaded and DataService.ProfileReleasing are BindableEvents other services listen to.
  • The test harness needs fake profiles, which is why __setStoreForTest, __injectProfileForTest and friends exist on the service.
  • Per-player rates are clamped when read (EconomyService.sane, Guard.flag), so a corrupted or hostile value cannot be written back into the store in a worse state.