TDD-003

DataStore Schema

Document Information

Purpose

Dokumen ini membuat konkret & typed skema DataStore yang menyimpan profil permanen satu pemain, berdasarkan "Data to Persist" dan "Data Structure" di GDD-012.

Revisi Besar (Version 2.0.0, Juli 2026). Tiga perubahan: (1) seluruh kepemilikan equipment dihapus dari skema — senjata, ammo, dan Utility kini isi paket Varian yang bersifat session-bound (GDD-004 v2.0.0); (2) Crystal masuk sebagai mata uang kedua; (3) dua kelompok field baru untuk state berjangka waktu — patroli Companion dan pengiriman Caravan (DN-010, DN-011).

Prinsip: Apa yang Tidak Masuk Skema

Batas ini sama pentingnya dengan field yang ada, karena ia yang menjaga model sewa tetap aman:

DataStore Key Convention

Schema — profile & economy

FieldTipeCatatan
schemaVersionnumberUntuk migrasi field, lihat Data Migration di bawah.
profile.userIdnumber
profile.usernamestringCache tampilan, bukan sumber kebenaran identitas.
profile.createdAtnumber (unix timestamp)
profile.lastLoginAtnumber (unix timestamp)
economy.coinnumberTidak pernah ditulis langsung dari client.
economy.crystalnumber(baru v2.0.0) Mata uang kedua (GDD-006 Currency). Sumber: milestone Caravan, Weekly Mission, Daily Login, pembelian Robux.

Schema — inventory

Direvisi total (v2.0.0). Seluruh kelompok equipment.* dihapus: ownedWeapons, ammoCounts, utilityCounts, dan activeLoadout. Tidak ada satu pun yang permanen lagi.

FieldTipeCatatan
inventory.storagearray<{ animalId, count }>Permanent Storage. Diisi lewat ekstraksi di Village dan Collect Companion — keduanya wajib lewat satu fungsi server yang sama supaya validasi dan logging tidak berbeda antar jalur.
inventory.consumablesmap<itemId, count>(baru v2.0.0) Consumable yang dibeli pemain dan dimiliki permanen (mis. Teleport Stone). Dipilih di layar Loadout untuk dibawa; sisa yang tidak terpakai kembali ke sini saat ekstraksi — bukan hangus, karena pemain sudah membelinya. Berbeda dari senjata dan ammo bawaan Varian yang session-bound dan selalu musnah.

Schema — collection & progression

FieldTipeCatatan
collectionmap<speciesId, count>Persis Collection Data MDD-007 — satu field per spesies, tidak ada weight/sub-loot.
progression.levelnumberMDD-006.
progression.expnumberMDD-006.
progression.lifetimePrestigenumberCatatan permanen, tidak pernah di-reset. Sumber seluruh unlock permanen (Item Delivery, Trading, Marketplace, Wraith Isle) — bukan lagi sumber Hunter Rank sejak DN-001 dipisah jadi dua counter. Rank tidak disimpan sebagai field terpisah — selalu dihitung ulang dari threshold MDD-006 saat dibaca, supaya tidak drift kalau threshold berubah.
progression.seasonalPrestigenumber(baru Juli 2026) Diisi tangkapan yang sama dengan lifetimePrestige, tetapi di-reset tiap musim. Inilah sumber Hunter Rank (DN-001).
progression.seasonIdstring(baru Juli 2026) Musim yang sedang berjalan saat profil terakhir ditulis. Reset dilakukan saat profil dibaca, bukan lewat sapuan massal: bila seasonId tidak cocok dengan musim aktif, seasonalPrestige dinolkan lalu seasonId diperbarui. Pola yang sama dipakai kematangan patroli Companion — pemain offline tidak pernah membebani server.
progression.seasonHistoryarray(baru Juli 2026) Riwayat rank akhir tiap musim yang sudah lewat, sumber Medali Musim di Trophy Room (DN-001, MDD-007). Wajib ada sejak rilis meski baru terpakai setelah musim pertama berakhir — bila field ini belum ada saat Season 1 selesai, rank akhir seluruh pemain pada musim itu hilang selamanya dan tidak dapat dipulihkan.
achievementsmap<achievementId, unlockedAt>Post-MVP (GDD-012 Post-MVP Persistence Scope).

Schema — mount & character

FieldTipeCatatan
cosmetics.ownedMountsarray<mountId>MNT0001–0003/0009 (beli-langsung) dan MNT0004–0006 (hasil evolusi) memakai field yang sama — tidak dibedakan sumbernya di schema, cukup ownership.
cosmetics.ownedCharactersarray<characterId>CHR0001–0003 (Valdrik/Nayra/Bonoo, gratis) selalu ada default di profil baru; CHR0004–0005 (Freya/Ragnar) baru masuk array setelah dibeli lewat jalur Coin atau Robux.
cosmetics.ownedSkinsarray<skinId>Termasuk Skin Outfit & Skin Mount (MDD-003).
cosmetics.ownedAccessoriesarray<accessoryId>ACC0001–0003 dst, universal ke semua Mount.
cosmetics.equipped{ mountId, characterId, mountSkinId, outfitSkinId, accessoryId }Semua nullable/opsional.

Dihapus (v2.0.0): companion.crystalCount — Crystal kini mata uang dan pindah ke economy.crystal. Field lama tidak digunakan ulang.

Schema — companion

Baru (v2.0.0, DN-010). Kelompok field untuk sistem berburu offline. Bagian paling sensitif keamanannya ada di activePatrol.slots.

FieldTipeCatatan
companion.ownedarray<{ companionId, level, biome, capacity }>Level dan biome ditetapkan sekali saat diperoleh, tidak pernah berubah (tidak ada sistem EXP Companion).
companion.fuelCountsmap<fuelId, count>Stok fuel per tier (FUL0001 dst, MDD-003).
companion.activePatrol{ companionId, startedAt, endsAt, slots } | nullHanya satu patroli aktif per Companion. endsAt adalah timestamp selesai, bukan sisa waktu — supaya tetap benar setelah pemain logout.
companion.activePatrol.slotsarray<{ animalId, ripeAt, collected }>Seluruh isi sudah ditentukan sejak Start (seed = userId + startedAt). Server hanya boleh mengirim slot yang ripeAt-nya sudah terlewat ke client — sisanya tidak pernah keluar dari server, dalam bentuk apa pun. Ini yang mencegah exploit "mengintip" hasil.

Schema — caravan

Baru (v2.0.0, DN-011). Seluruhnya state per-pemain — tidak ada state lintas server, sehingga asumsi "session locking antar-server tidak dibutuhkan untuk MVP" di bawah tetap berlaku utuh.

FieldTipeCatatan
caravan.availableOrdersarray<{ orderId, npcId, package, value, travelSeconds, expiresAt }>Pesanan personal yang sedang ditawarkan ke pemain ini (acuan 2–3 sekaligus, berotasi). value dihitung server dari Base Price × pengali.
caravan.activeShipmentsarray<{ shipmentId, orderId, endsAt, claimed }>Pengiriman berjalan. endsAt timestamp selesai, bukan hitung mundur. Jumlah dibatasi slot.
caravan.slotCountnumberJumlah Caravan yang boleh berjalan bersamaan; dapat ditambah lewat pembelian.
caravan.tradeValueAccumulatedDihapus (Juli 2026) — field ini satu-satunya gunanya menggerakkan milestone Crystal, dan milestone itu dicabut (DN-011). Menyimpan akumulasi yang tidak dibaca siapa pun hanya menambah beban tulis tiap pengiriman. Nama tidak digunakan ulang.

Schema — trophy room, weekly mission & settings

FieldTipeCatatan
trophyRoom.capacityTiernumberMDD-007 (5/15/28 slot).
trophyRoom.displayedarray<{ speciesId, pedestalType, slot }>
weeklyMission.weekIdstringDipakai untuk deteksi reset mingguan.
weeklyMission.objectiveProgressnumberGDD-013.
weeklyMission.claimedThisWeekboolean
settingsmap<key, value>Post-MVP, minimal untuk MVP (mis. toggle audio).

Save Triggers

Menerjemahkan Save Triggers GDD-012 ke pola teknis konkret:

Data Safety

Data Migration

Field baru selalu punya default value aman dan ditambahkan lewat kode baca (bukan mengubah data lama secara massal). Perubahan struktur besar (mis. mengganti bentuk field yang sudah ada) memakai schemaVersion untuk menjalankan fungsi migrasi satu kali saat profil lama pertama kali dibaca, sebelum disimpan ulang dalam bentuk baru — konsisten dengan prinsip CDR-007 "Expandable Foundation" dan GDD-012 Data Migration.

Dependencies