Data Schema
This page is hidden behind SLKARDS' spoiler gate. There's a way to unlock it. If you know the way, use it — otherwise, keep exploring the game.
Data Schema
[REDACTED]
Section titled “[REDACTED]”SLKARDS uses a MongoDB + Redis architecture optimised for medium scale (50–500 servers, 1K–10K players) on a self-hosted VPS.
[REDACTED]
Section titled “[REDACTED]”Handles all persistent game data: player profiles, kard instances, alliance state, marketplace listings, logs, and static definitions. MongoDB’s document model maps naturally to SLKARDS’ entity structure — a player profile, a kard instance, and an alliance are all self-contained documents with nested properties.
[REDACTED]
Section titled “[REDACTED]”Handles high-frequency, short-lived data that benefits from sub-millisecond access:
- Active battle state: Created on battle start, destroyed on battle end. Battles involve rapid turn-by-turn reads/writes that would thrash MongoDB unnecessarily.
- Active spawn state: Short-lived minigame sessions (1–10 minutes). Created on spawn, destroyed on conclusion.
- Cooldowns & timers:
/dailycooldown checks, spawn timers, rate limiting. Redis TTL keys auto-expire. - Leaderboard caching: Redis sorted sets (
ZADD,ZRANK,ZREVRANGE) are purpose-built for ranked leaderboards. Recalculate from MongoDB on schedule (e.g., daily for ELO percentiles) and serve from Redis. - Session locks: Prevent double-spending during trades, marketplace purchases, and pack openings.
[REDACTED]
Section titled “[REDACTED]”- Hot/cold split. Frequently accessed player data (
player_core) is separated from on-demand stats (player_stats). Common operations (koins, GP, daily claim, battles) never load granular stat counters. - Extensible arrays over hardcoded fields. Series-specific stats use
[{series: "Spectral", count: 5}]arrays instead ofSPCollectedfields. Automatically supports custom player-created series. - Deduplicated battle stats. A single battle stats structure with a
battleTypediscriminator replaces three identical nested objects. - Append-only logs. Historical data (battles, trades, wipes, admin actions) is write-once. Logs are never edited, only appended.
- Compound indexes on hot paths. Every query that runs on common operations (spawn, battle, trade, daily) must hit an index. See Section 9 for the full index strategy.
[REDACTED]
Section titled “[REDACTED]”These collections define the “rules” of the game. They are read-only for the bot and shared across all servers. Cache these in memory on bot startup and refresh only on manual reload.
[REDACTED]
Section titled “[REDACTED]”Stores the master definition for every kard in the game (397 base + 17 Almighty (negative IDs) + custom). Backed on disk by data/kardData.json; loaded via StaticRegistry in cogs/_bootstrap.py.
kardID(Number): Unique ID. Base series: 0–396. Almighty: -17 to -1. Custom: 397–9999.kardSeries(String): “Quirky Cats”, “Ultimate Arcana”, “Almighty”, etc.kardName(String): The kard’s display name.kardDescription(String): The kard’s flavor text.kardRarity(Number): 1 - 10. 1 to 5 stars for base series, Almighties are 7, Omnipotence is 10.kardModifier(String, default:null): The kard’s natural modifier if any (“Animated”, “Pixelated”, “Glitched”, “Negative”, “Polychrome”, “Sketched”).kardImagePath(String): Filepath to the kard’s image (e.g.,"136.png").isSpawnable(Boolean, default:true):falsefor Almighty kards (unlocked via prestige only).isCustom(Boolean, default:false):trueif this is a player-created kard from a custom series.kardCreator(String): The creator’s display name.kardCreatorID(String, default:null): Discord user ID of the player who made it (only for custom kards).
[REDACTED]
Section titled “[REDACTED]”Stores the master definition for every series, including type matchups. Backed on disk by data/static_series.json.
seriesName(String): Unique ID. “Ultimate Arcana”, “Spectral”, etc.seriesAbbreviation(String): “UA”, “SP”, “AB”, etc. Custom series get auto-generated abbreviations.colorCode(Number): Decimal color value for embeds.logos(Array of Strings): Filepaths for series logo variants.kardCount(Number): Number of kards in the series (25 for base and custom, 22 for UA, 17 for Almighty).rarityDistribution(Object):{ "1star": 9, "2star": 7, "3star": 5, "4star": 3, "5star": 1 }(varies by series).modifierSlots(Array of Objects): Which kards in this series have natural modifiers.kardID(Number)modifier(String)
typeAdvantages(Array of Strings): Series names this series is super-effective against (2x damage). Empty[]for custom series.typeDisadvantages(Array of Strings): Series names this series is not-effective against (0.5x damage). Empty[]for custom series.isCustom(Boolean, default:false):truefor player-created series.creatorUserID(String, default:null): Discord user ID of the creator. Only set for custom series.canPrestige(Boolean, default:true):falsefor custom series.unlockCondition(Object, default:null): For series requiring unlock conditions (e.g., Almighty kards).null= freely available. Supports varied unlock types per series.conditionType(String): “secretPhrase”, “timeBased”, “koinCost”, “achievementRequired”, “custom”.conditionValue(Mixed): The condition parameter — a phrase string, a duration in hours, a koin amount, an achievement ID, or a custom logic identifier.conditionDescription(String): Human-readable hint shown to the player (e.g., “Speak the ancient words to unlock this Almighty”).
[REDACTED]
Section titled “[REDACTED]”Stores the definition for every unlockable achievement. Content lives on disk in data/static_achievements.json and is loaded by the static registry, but the achievement-unlock pipeline ships in 0.6._
achievementID(String): Unique ID (e.g., “FIRST_KARD_COLLECT”, “NG_BATTLE_WIN_100”).achievementName(String): The achievement’s display title.achievementDescription(String): Explanation of the unlock condition.achievementCategory(String): “Kards”, “Koins”, “Battling”, “Trading”, “Wipes”, “Prestiging”, “Alliances”, “Events”, “Marketplace”, “Other”.achievementType(String): “MAIN”, “NG_PLUS”, “SECRET”.achievementReward(Object):koins(Number): Koin reward on unlock (50–2500).title(String, default:null): Title unlocked (if any).footnote(String, default:null): Footnote unlocked (if any).
progressTarget(Number, default:null): For multi-stage achievements, the target count (e.g., 100 for “Collect 100 kards”).isHidden(Boolean, default:false):truefor SECRET achievements — don’t show until discovered.isRepeatable(Boolean, default:false):truefor NG+ achievements that reset each cycle.
[REDACTED]
Section titled “[REDACTED]”Stores the definitions for all packs in Ida’s shop. Backed on disk by data/static_packs.json.
packID(String): Unique ID (e.g., “BRONZE”, “SILVER”, “THEMED_CURSED”, “PARAGON”).packName(String): Display name (e.g., “Bronze Pack”, “Paragon Pack”).packCost(Number): Price in koins.packRevealed(Number): How many kards are shown (typically 3).packPicks(Number): How many kards the player keeps (typically 2).packRarityOdds(Object):{ "1star": 48, "2star": 40, "3star": 10, "4star": 1, "5star": 1 }(percentages).packSeriesOdds(Object, default:null): If null, all series are equally weighted. Otherwise,{ "Cursed": 50, "Twisted Dreams": 50 }for themed packs.packRequiresNG(Boolean, default:false):truefor Paragon Pack (NG+ only).
[REDACTED]
Section titled “[REDACTED]”Stores definitions for Colossal and Primordial raid bosses. The content file data/static_raid_bosses.json exists today and renders in places that reference boss data, but the raid system itself ships with the 0.7 Alliances release.
bossID(String): Unique ID (e.g., “EFFIGY”, “ZENITH”, “PRIMORDIAL_1”).bossName(String): Display name.bossType(String): “Colossal” (single-alliance) or “Primordial” (coalition).difficulty(String): “Easy”, “Medium”, “Hard”, “Very Hard”, “God-Tier”.unlockLevel(Number): Alliance level required to initiate (5, 10, 15, 20, 25, 30, 35, 40, 50).baseHP(Number): Base hit points.hpScaling(Object):{ "perMember": Number, "perAllianceLevel": Number }— how HP scales.attackPattern(Array of Objects): Boss attack sequence per turn.rewardPool(Object): Koins, GP, and special drops on defeat.
[REDACTED]
Section titled “[REDACTED]”Stores definitions for all purchasable cosmetics (titles, footnotes, narrators, emojis). The disk file data/static_customization.json exists as an empty stub; titles/footnotes/narrators/rarity emojis ship with 0.6 alongside /slkards profile.
itemID(String): Unique ID (e.g., “TITLE_KARD_NOVICE”, “NARRATOR_PIRATE”).itemType(String): “title”, “footnote”, “narrator”, “rarityEmoji”.itemName(String): Display name.itemSource(String): “achievement”, “prestige”, “zeno”, “premium”, “event”, “alliance”.itemCostGP(Number, default:null): GP cost if purchased from Zeno’s.isPremium(Boolean, default:false): Requires Premium Personal Pass.unlockCondition(String, default:null): Description of how to earn it (for achievement/prestige-based items).
Implementation note — Narrators, Custom Emojis, Titles, Footnotes: These are all covered by
static_customization. Narrator voice lines are stored as text templates within the narrator’s customization document (e.g.,{ itemID: "NARRATOR_PIRATE", dialogueTemplates: { attack: "Arrr, {player} fires a cannonball!", critical: "Blimey! A devastating blow!" } }). Custom Discord emojis are loaded from the on-disk static-customization file. Titles and footnotes are individual entries instatic_customization.
[REDACTED]
Section titled “[REDACTED]”These collections manage global and server-specific configurations.
[REDACTED]
Section titled “[REDACTED]”Documents the canonical global-default values for the bot. Not a live Mongo collection — these values live in data/static_*.json, data/constants.py, and data/kardData.json and are read into memory by StaticRegistry.
_id(String): “constants” — the static ID for this single document.customEmojis(Object): Emoji IDs for all custom Discord emojis used by the bot.kardImagesBasePath(String): Base filepath for kard images (files named{kardID}.png).npcPortraits(Object): Filepaths for each NPC’s portraits (The Collector, Ida, Zeno, Dr. Higginbotham).rankLogos(Object): Filepaths for each ranked tier’s logo (F- through S+).sealOWipeLogos(Object): Filepaths for each Seal o’ Wipes badge (I through X, golden skull for 10+).nullKardImage(String): Fallback filepath when no kard image is found.nullSeriesData(Object): Fallback series data.seriesName(String)colorCode(Number)logos(Array of Strings)
gpUpgradeCosts(Object): Key-value costs for all Zeno’s Colosseum items.wishlistTiers(Array of Numbers): [10, 20, 30]marketplaceTiers(Array of Numbers): [20, 30, 50]binderTiers(Array of Numbers): [30, 30, 30, 30, 30]idaDiscountTiers(Array of Numbers): [30, 40, 50]limitbreakerTiers(Array of Numbers): [30, 50, 150]futureDiary(Number): 25juggernautSuitBase(Number): 20philosopherStoneBase(Number): 20warpedPocketWatchBase(Number): 10zenoDiary(Number): 1000theCounter(Number): 100
packsDailyLimit(Number): Max pack purchases per player per day.secretShopPhrase(String): The phrase to unlock Zeno’s Secret Shop.wipeMinHours(Number): Min range of wipe timer (maps to ~3 weeks).wipeMaxHours(Number): Max range of wipe timer (maps to ~6 weeks).spawnMinHours(Number): Default min spawn interval.spawnMaxHours(Number): Default max spawn interval.allianceCreationCost(Number): 15,000 koins.allianceSalesTax(Number): Tax rate for alliance shop purchases.allianceTaxBrackets(Array of Objects): Income tax brackets for member wages. Progressive — each bracket applies to wages at or aboveminWageand below the next bracket’sminWage. Default brackets:[{minWage: 0, taxRate: 0.05}, {minWage: 500, taxRate: 0.10}, {minWage: 1000, taxRate: 0.15}, {minWage: 2500, taxRate: 0.20}, {minWage: 5000, taxRate: 0.30}]. Tax Exemption shop upgrades reduce all rates by 5% per level (min 0%).minWage(Number): Lower bound of this bracket (inclusive).taxRate(Number): Decimal rate applied to gross wage (e.g.,0.15= 15%).
allianceLevelingCurve(Array of Objects): XP required per alliance level. Generated from the formularound(500 × level^1.5, nearest 100). Populated at bot startup for levels 1–200+ and cached.level(Number)xpRequired(Number): XP needed to advance from this level to the next.memberCap(Number): Member capacity unlocked at this level (10 at L1, scaling to 50 at L40).
allianceXPSources(Object): XP awarded to an alliance for each activity type.memberDaily(Number): XP per member/dailyuse. Default: 5.memberRankedWin(Number): XP per ranked battle win by a member. Default: 15.memberEventReward(Number): XP per event reward earned by a member. Default: 25.donationByStar(Array of Numbers): XP per kard donated indexed by rarity (index 0 unused; index 1 = 1★, … index 5 = 5★). Default:[0, 10, 25, 60, 150, 400].dexSeriesComplete(Number): XP for completing a series in the Alliance Dex. Default: 500.raidComplete(Object): XP on raid defeat, keyed by difficulty. Default:{ Easy: 1000, Medium: 2000, Hard: 3500, "Very Hard": 5000, "God-Tier": 10000 }.avaWin(Number): XP for winning an AvA. Default: 800.avaLoss(Number): XP participation consolation for losing an AvA. Default: 100.coalitionRaidMaxXP(Number): Max XP for a coalition raid, distributed proportionally by damage share. Default: 3000.
allianceShopPerks(Object): Definitions and costs for all alliance shop upgrades.allianceShopBoons(Object): Definitions and costs for all alliance boons.duplicateSellingPrices(Object): Koins earned per rarity when selling to The Collector.1star(Number): 252star(Number): 603star(Number): 1504star(Number): 3505star(Number): 800
artificialCosts(Object): Dr. Higginbotham pricing and timers.rarityStars(Array of Objects):[{cost: 500, duration: "12h"}, {cost: 1000, duration: "1d"}, ...]modifierAdd(Object):{cost: 2000, duration: "1d"}modifierChange(Object):{cost: 1000, duration: "12h"}modifierRemove(Object):{cost: 300, duration: "instant"}limitbreakerStars(Array of Objects): Per-tier costs/durations.limitbreakerModifier(Object):{cost: 5000, duration: "3d"}evolutionPrep(Object):{cost: 15000, duration: "7d"}
battleStats(Object): HP ranges, damage tables, accuracy tables, and type chart.minigamePool(Object): Minigame definitions keyed by rarity availability.
[REDACTED]
Section titled “[REDACTED]”Stores the settings and configuration for each individual server.
guildID(String): Unique ID (Discord Server ID).setupComplete(Boolean, default:false): Whether the setup wizard (/admin-slkards setup) has been run to completion. Used to gate bot functionality and prompt admins to run the wizard.adminRoleID(String): Role that can use admin/debug commands.playerRoleID(String): Role for players, used for mentions.slkardsCategoryID(String): Discord category ID for the main SLKARDS category containing all server-wide channels.channels(Object): Discord channel IDs for all server-wide SLKARDS channels. Created during the setup wizard (see Bot Architecture for permissions and bot behavior per channel).announcementsChannelID(String): Server announcements and system messages (ranked resets, major updates).premiumChannelID(String): Premium feature information embed.eventsChannelID(String): Event lifecycle — start, progress, results.wipesChannelID(String): Wipe countdown warnings and execution notices.leaderboardsChannelID(String): Persistent leaderboard embeds (current season ELO, prestige count, NG+ cycles, total kards collected, total koins received, minigames won, AvA wins by alliance).adminChatChannelID(String): Admin-only discussion channel.adminCommandsChannelID(String): Admin and debug command execution.spawnChannelID(String): Where kards spawn and spawn pool threads are created.battlesChannelID(String): Battle challenge and battle thread creation.tradingChannelID(String): Trade initiation and trade thread creation.storesChannelID(String): Ida’s Packs, Dr. Higginbotham’s Lab, and Marketplace interactions.playerChatChannelID(String): General player discussion and social commands.alliancesChatChannelID(String): Alliance discovery, creation, and joining.
threadCleanupPolicy(String, default: “delete_5m”): How completed interaction threads are handled. Options: “delete_5m” (auto-delete after 5 minutes), “archive_1h” (auto-archive after 1 hour), “archive_24h” (auto-archive after 24 hours). Configurable by re-running/admin-slkards setupin repair mode (the standalone/admin-slkards threadcleanupwas removed in 0.3.x).bannedUserIDs(Array of Objects): Players banned from using the bot on this server.userID(String): The banned player’s Discord ID.reason(String, default:null): Optional ban reason.bannedTimestamp(Date): When they were banned.bannedByUserID(String): Which admin issued the ban.
spawnMinHours(Number): Server’s custom minimum spawn interval.spawnMaxHours(Number): Server’s custom maximum spawn interval.nextSpawn(Date): Scheduled time for the next spawn.nextWipe(Date): The secret scheduled time for the next wipe.lastWipe(Date): Timestamp of the most recent wipe.wipeCount(Number): Total wipes that have occurred on this server.rankedSeason(Object): Tracks the current ranked season for this server.seasonNumber(Number): Current season count (starts at 1).seasonStartDate(Date): First day of the current season (1st of the month).seasonEndDate(Date): Last moment of the current season (end of the month).lastResetTimestamp(Date): When the last season reset was processed. Used to prevent double-processing.
lastSpawnWatchData(Object): Tracks the Warped Pocket Watch consumable state for the most recent spawn. Resets each time a new kard spawns. The watch lets a player respawn the latest kard (captured or expired) at a cost based on rarity. Each player may only use the watch once per wipe, and each spawn has a limited number of server-wide uses based on rarity.kardID(Number): The kard from the last spawn.kardRarity(Number): Rarity of the last spawned kard (determines cost and use cap).usesRemaining(Number): Server-wide uses left for this spawn. Initialized from rarity (1★: 5, 2★: 4, 3★: 3, 4★: 2, 5★: 1). Decremented on each successful watch purchase. When 0, no more players can use the watch for this spawn.usedByUserIDs(Array of Strings): UserIDs who have purchased the watch for this spawn. Prevents double-use on the same spawn (secondary check — the primary per-wipe limit is onplayer_core.watchUsedThisWipe).
[REDACTED]
Section titled “[REDACTED]”[REDACTED]
Section titled “[REDACTED]”The main profile for a player. One document per player, per server. Contains only the data accessed during common operations (spawns, battles, trades, daily claims). Compound unique index on [userID, guildID].
userID(String): The player’s Discord User ID.guildID(String): The server ID this profile belongs to.registeredTimestamp(Date): When the player first registered.koinsBalance(Number): Current koin balance.gpBalance(Number): Current Glory Points balance.currentElo(Number): Ranked ELO (default: 1000).currentRank(String): Calculated rank (e.g., “C+”, “S-”).dailyLastClaim(Date): Timestamp of last/daily.dailyStreak(Number, default: 0): Consecutive days claimed. Resets on miss. Max bonus at 14 (240 koins streak bonus).kardWishlist(Array of Numbers): List ofkardIDs (default capacity: 3, max: 10).allianceID(ObjectId, default:null): Reference toactive_alliances.secretCommandAttempts(Number): Daily attempts for/slkards secret.watchUsedThisWipe(Boolean, default:false): Whether this player has used the Warped Pocket Watch this wipe. Resets tofalseon each server wipe.secretCommandLastAttempt(Date): Timestamp to reset attempts.profile(Object): Player profile data, displayed publicly.title(String, default:null): Chosen title from unlocked cosmetics.favoriteKardID(ObjectId, default:null): Reference toplayer_binders.footnote(String, default:null): Chosen footnote.rarityEmoji(String, default:null): Chosen rarity emoji set.battleNarrator(String, default:null): Chosen battle narrator.showPremiumIcon(Boolean, default:true): Toggle premium icon visibility.
upgrades(Object): Tracks GP purchases from Zeno’s Colosseum.wishlistLimit(Number, default: 3): Current max wishlist size (3→5→7→10).marketplaceLimit(Number, default: 1): Current max marketplace listings (1→2→3→5).binderCapacity(Number, default: 220): Current binder capacity (220→264→308→352→396→440).idaDiscountPercent(Number, default: 0): Pack discount tier (0→10→20→30%).limitbreakerTier(Number, default: 0): Unlocked LB tier (0→1→2→3).
consumables(Object): Tracks purchasable item charges.futureDiaryCharges(Number, default: 0): Remaining uses this wipe (max 5).juggernautSuitBoughtThisWipe(Number, default: 0): Whether the player has purchased a new Juggernaut Suit during the current wipe cycle (0 = not yet, 1 = purchased). Resets to 0 after each wipe. A player may purchase at most one new suit per wipe cycle. This gate is independent of how many suits are already equipped on kards — a player whose suit was not consumed last wipe can still buy a new one this cycle and stack it on a different kard.philosopherStoneUsedThisWipe(Number, default: 0): Uses this wipe (max 2).warpedPocketWatchUsedThisWipe(Number, default: 0): Uses this wipe (max 1).zenoDiaryPurchased(Boolean, default:false): One-time purchase.counterValue(Number, default: 0): The Counter’s current value (increments on each 100GP purchase).
unlocks(Object): All cosmetics the player has earned or purchased.unlockedTitles(Array of Strings): Title IDs.unlockedFootnotes(Array of Strings): Footnote IDs.unlockedNarrators(Array of Strings): Narrator IDs.unlockedEmojis(Array of Strings): Rarity emoji set IDs.
prestigeProgress(Object): Tracks prestige counts per series.series(Array of Objects):[{ seriesName: "Spectral", count: 2 }, { seriesName: "Almighty", count: 1 }, ...]totalPrestiges(Number): Sum of all prestige counts.
almightyUnlocked(Array of Numbers): AlmightykardIDs unlocked through prestige (not necessarily currently owned).customSeriesRedeemed(Boolean, default:false):trueonce the player has used at least one custom series voucher. Used as a quick check for whether to show custom series UI elements.customSeriesVouchers(Number, default: 0): Available/slkards customusages. Increments by 1 on each Almighty prestige, decreases by 1 each time a custom series is made.ngPlus(Object): New Game+ tracking.cycleCount(Number, default: 0): Current NG+ cycle (0 = not in NG+, 1 = NG+1, etc.).lastCycleTimestamp(Date, default:null): When the current cycle began.gpMultiplier(Number, default: 1.0): GP earn rate multiplier (1.25 at NG+1, capped at 1.5 at NG+5).dailyBonusKoins(Number, default: 0): Extra koins per /daily from NG+ (25 per cycle, capped at 400 at NG+16).binderBonus(Number, default: 0): Extra binder capacity from NG+. Set to 60 on first NG+ activation (increasing base from 220 to 280). Stacks with Zeno’s binder upgrades for a max of 500 (440 + 60).unlockedTitles(Array of Strings): NG+ exclusive titles earned (“Reborn”, “Twice Tempered”, “Thrice Ascended”, “Quadra Legend”, “Eternal”).
[REDACTED]
Section titled “[REDACTED]”On-demand stats for a player. Queried only when the player uses /stats, /leaderboard, or checks achievements. One document per player, per server. Compound unique index on [userID, guildID].
Cross-collection note for the stats dashboard: The
/statscommand renders data from bothplayer_coreandplayer_statsin a single view. Fields likecurrentElo,currentRank,prestigeProgress(total prestiges, per-series counts), andngPlus.cycleCountlive inplayer_corebecause they’re needed for hot-path operations (battles, prestige checks, leaderboards). They are NOT duplicated here — the stats dashboard simply reads both documents in parallel. This avoids sync drift while keeping common operations fast.
userID(String): The player’s Discord User ID.guildID(String): The server ID.lifespan(Number): Hours played on this server.commandsUsed(Number): Total commands used.dailyCommandsUsed(Number): Total/dailyuses.achievementsUnlocked(Number): Total achievements unlocked.collection(Object):totalKardsCollected(Number): Lifetime total kards obtained (including wiped/traded away).currentKardsOwned(Number): Kards currently in binder. Denormalized for performance — this could alternatively be computed viaplayer_binders.countDocuments({ownerUserID, guildID}), but storing it here gives O(1) lookups for profiles, leaderboards, and achievement checks without a count query. The trade-off: this must be incremented/decremented on every kard gain/loss (spawn win, trade, wipe, prestige, marketplace). To guard against desync, run a periodic reconciliation job (e.g., weekly) that recounts fromplayer_bindersand corrects any drift.seriesCollected(Array of Objects):[{ seriesName: "Spectral", count: 12 }, ...]raritiesCollected(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }modifiersCollected(Object):{ "animated": N, "negative": N, "polychrome": N, "glitched": N, "pixelated": N, "sketched": N }
minigames(Object):totalMinigamesWon(Number): Aggregate count of all minigame wins across all types. Denormalized for leaderboard performance — thewonarray below has per-minigame breakdowns, but summing an array on every leaderboard refresh is expensive at scale. This field is incremented alongside the per-minigame counter on every win.played(Array of Objects):[{ minigame: "wordWarp", count: N }, { minigame: "flagGuesser", count: N }, ...]won(Array of Objects):[{ minigame: "wordWarp", count: N }, ...]
battles(Array of Objects):battleType(String):"casualRandom", "casualSet", "ranked"played(Number): How many battles the player has played.won(Number): How many battles the player won.longestWinstreak(Number): The longest winstreak.currentWinstreak(Number): The current winstreak.attacksUsed(Object): How much each attack has been used (i.e.,{light: N, normal: N, critical: N, superEffective: N, notEffective: N })skillsUsed(Object): How much each skill has been used (i.e.,{invertedMirror: N, blackout: N, ...})gambitsActivated(Object): How much each gambit has been activated (i.e.,{singularity: N, jackpot: N, ...})evolutionsTriggered(Number): How many evolutions were triggered.
rankedSeasons(Array of Objects): Historical record of a player’s ranked performance per season.seasonNumber(Number): The season this entry corresponds to.finalElo(Number): ELO at season close (before soft reset).finalRank(String): Rank letter at season close.peakElo(Number): Highest ELO achieved during the season.peakRank(String): Highest rank achieved during the season.gamesPlayed(Number): Ranked games played this season.gamesWon(Number): Ranked games won this season.seasonRewardKoins(Number): Koins awarded at season end.seasonRewardGP(Number): GP awarded at season end.
trades(Object):tradesCompleted(Number)tradeRep(Number): Net reputation (+rep minus -rep).lastRepGivenTimestamp(Date, default:null): Timestamp of the last time this player gave rep to another player. Used to enforce the one-rep-per-day cooldown. Reset logic: if the current date differs from this timestamp’s date (server timezone), the player is eligible to give rep again.kardsTraded(Object):totalKards(Number)seriesTraded(Array of Objects):[{ seriesName: "Spectral", count: N }, ...]raritiesTraded(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }modifiersTraded(Object):{ "animated": N, "negative": N, ... }
gp(Object):totalGPReceived(Number)gpPurchases(Object): Counts of each item type purchased.wishlistUpgrades(Number)marketplaceUpgrades(Number)binderUpgrades(Number)packDiscounts(Number)limitbreakerUnlocks(Number)customEmojis(Number)battleNarrators(Number)titles(Number)footnotes(Number)futureDiaries(Number)juggernautSuits(Number)philosopherStones(Number)warpedPocketWatches(Number)zenoDiaries(Number)counterPurchases(Number)
koins(Object):totalKoinsReceived(Number)koinSources(Object):duplicateSales(Number)prestige(Number)dailyCommand(Number)events(Number)marketplace(Number)achievements(Number)allianceWages(Number)ngPlusBonuses(Number)
totalKoinsSpent(Number)koinSinks(Object):packs(Number)artificialValues(Number)limitbreakers(Number)marketplace(Number)allianceCreation(Number)allianceDonations(Number)
prestige(Object):customSeriesCreated(Number)
wipes(Object):totalWipesExperienced(Number)kardsWiped(Object):totalKardsLost(Number)raritiesWiped(Object):{ "1star": N, "2star": N, ... }seriesWiped(Object):{ "Ultimate Arcana": N, "Spectral": N, ... }modifiersWiped(Object):{ "animated": N, ... }artificialsWiped(Number): Kards with artificial values lost.
wipeSurvivors(Object): How many kards the player currently owns at each Seal o’ Wipes tier{ "1wipe": N, "2wipes": N, ..., "10wipes": N, "10plus": N }
artificials(Object):artificialRaritiesApplied(Number)artificialModifiersApplied(Number)limitbreakerRaritiesApplied(Number)limitbreakerModifiersApplied(Number)evolutionsPrepared(Number)
events(Object):competitionEventsParticipated(Number)competitionEventsTop1Percent(Number): Times placed in top 1%.competitionEventsTop5Percent(Number): Times placed in top 5%.competitionEventsTop10Percent(Number): Times placed in top 10%.competitionEventsTop25Percent(Number): Times placed in top 25%.competitionEventsTop50Percent(Number): Times placed in top 50%.taskEventsParticipated(Number)taskEventsAllTasksCompleted(Number): Times completed ALL tasks in a task event.individualTasksCompleted(Number): Total individual tasks completed across all events.coopEventsParticipated(Number)coopGoalsContributed(Number): Total co-op goals the player contributed to.coopGoalsContributionTop1Percent(Number): Times placed in top 1%.coopGoalsContributionTop5Percent(Number): Times placed in top 5%.coopGoalsContributionTop10Percent(Number): Times placed in top 10%.coopGoalsContributionTop25Percent(Number): Times placed in top 25%.coopGoalsContributionTop50Percent(Number): Times placed in top 50%.totalEventKoinsEarned(Number)totalEventGPEarned(Number)
duplicates(Object):totalSold(Number)soldByRarity(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }
packs(Object):packsOpened(Number)kardsFromPacks(Object):totalKards(Number)byRarity(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }bySeries(Object):{ "Ultimate Arcana": N, "Spectral": N, ... }byModifier(Object):{ "Negative": N, "Glitched": N, ... }
marketplace(Object):salesCreated(Number): Listings posted as direct sales.salesCompleted(Number): Direct sales that sold successfully.salesPurchased(Number): Direct sale listings bought from other players.auctionsCreated(Number): Auction listings posted.auctionsCompleted(Number): Auctions that concluded with a winner.auctionsParticipated(Number): Auctions the player bid on.auctionsWon(Number): Auctions the player won.counterOffersMade(Number): Counter-offers sent on sale listings.counterOffersAccepted(Number): Counter-offers that were accepted by the seller.counterOffersReceived(Number): Counter-offers received on the player’s sale listings.totalSpent(Number): Total koins spent on marketplace purchases.totalIncome(Number): Total koins earned from marketplace sales (after fees).totalFeesPaid(Number): Total 5% transaction fees paid.
alliances(Object):alliancesJoined(Number)alliancesCreated(Number)vaultDonations(Number): Total kards donated to alliance vaults.wagesReceived(Number): Total koins received from alliance wages.taxesPaid(Number): Total koins lost to alliance income tax.raidParticipations(Number): Total raids the player attacked in.raidsWon(Number): Raids the player’s alliance defeated while the player participated.raidDamageDealt(Number): Cumulative raid damage across all raids.raidBossesDefeated(Array of Objects):[{ bossID: "EFFIGY", count: N }, ...]coalitionRaidParticipations(Number)coalitionRaidsWon(Number): Coalition raids defeated while the player participated.coalitionRaidDamageDealt(Number)avaParticipations(Number): Total AvA wars the player fought in.avasWon(Number): AvAs where the player’s alliance won.avasLost(Number): AvAs where the player’s alliance lost.avaBattlesWon(Number): Individual AvA battles won.avaBattlesLost(Number): Individual AvA battles lost.avaColosseumParticipations(Number)avaColosseumWins(Number): Colosseum tournaments where the player’s alliance won.
[REDACTED]
Section titled “[REDACTED]”Architecture note: player_binders is a single flat collection containing all kard instances across all players and servers. This is the optimal layout because:
- Trades are atomic: trading a kard is a single
updateOne({_id: kardInstanceID}, {$set: {ownerUserID: newOwnerID}})- no need for multi-document transactions or moving documents between collections. - Marketplace purchases are atomic: same single-field update.
- Cross-player queries are easy: “how many 5-star kards exist on this server?” is a simple
countDocuments({guildID, totalRarity: 5}). - The alternative (separate binder collection per player) would require two writes per trade (remove from seller’s array + add to buyer’s array), need MongoDB transactions for atomicity, and make cross-player queries expensive. The embedded approach would also hit MongoDB’s 16MB document limit for collectors with large binders.
[Shipped] — backed by repos/player_binders.py.
Stores every individual kard instance owned by all players. This is their “binder.” Each document represents one physical kard.
_id(ObjectId): Unique ID for this specific kard instance.kardID(Number): The static definition ID, referencingstatic_kards.ownerUserID(String): TheuserIDof the current owner.guildID(String): The server this kard exists on.obtainedTimestamp(Date): When this instance was first acquired.obtainedMethod(String): “spawn”, “pack”, “trade”, “philosopherStone”, “warpedPocketWatch”.sealOWipes(Number, default: 0): Number of wipes this kard has survived.isJuggernautEquipped(Boolean, default:false): This kard has a Juggernaut Suit equipped. During wipe execution, the kard is still included in the random selection pool. If selected for removal, the suit intercepts (kard is saved) and this flag is reset tofalse. If the kard is not selected, this flag remainstrueand the suit carries over to the next wipe cycle. Can betrueon multiple kards simultaneously if accumulated across wipes.artificialRarity(Number, default: 0): Additional stars from Dr. Higginbotham’s artificial system (0–5).artificialModifier(String, default:null): Modifier added via artificial system (“SEMI-NEGATIVE”, “SEMI-POLYCHROME”, etc.).limitbreakerRarity(Number, default: 0): Additional stars from Limitbreaker system (0–5). Same stat benefits as natural rarities.limitbreakerModifier(String, default:null): Modifier added via Limitbreaker system. Full-strength (not SEMI-).isEvolutionReady(Boolean, default:false):truewhen kard has 10-star total rarity + a modifier (any combination of natural/artificial/limitbreaker) and Omega Limitbreaker (Tier 3) has been applied.isLocked(Boolean, default:false):trueif the user locks it to prevent sale/trade/recycle.isInBattle(Boolean, default:false):trueif currently part of a set battle deck.isInMarket(Boolean, default:false):trueif listed onactive_market.isInLab(Boolean, default:false):trueif currently being modified at Dr. Higginbotham’s.labJob(Object, default:null): Active modification in progress.jobType(String): “artificialRarity”, “artificialModifier”, “limitbreakerRarity”, “limitbreakerModifier”, “evolutionPrep”, “modifierChange”, “modifierRemove”.startTimestamp(Date)completionTimestamp(Date)costPaid(Number): Koins spent on this job.
[REDACTED]
Section titled “[REDACTED]”Design choice: This collection uses the one-document-per-achievement pattern (also called the junction pattern). Each document represents a single player–achievement pair. This is better than embedding all 497 achievements in an array inside the player document because:
- Querying: “Has player X earned achievement Y?” is a fast indexed lookup:
findOne({userID, guildID, achievementID}). - Updates: Setting progress on one achievement doesn’t load/rewrite all 497.
- Size: 497 achievement objects embedded in
player_corewould add ~50KB+ per player, bloating every read. - Indexes: MongoDB can efficiently index individual fields on this collection but cannot efficiently index nested array elements for compound queries.
- The trade-off: fetching ALL of a player’s achievements requires
find({userID, guildID})returning many small documents, but this only happens when viewing/achievements(on-demand).
[Designed] — ships with 0.6 alongside /slkards achievements.
Tracks which achievements a player has unlocked on a server. One document per player-achievement pair.
userID(String): The player’s Discord User ID.guildID(String): The server ID.achievementID(String): The ID fromstatic_achievements.unlockedTimestamp(Date): When they earned it.ngPlusCycle(Number, default: 0): Which NG+ cycle this was earned in (0 = base game).progress(Number, default:null): Current progress toward multi-stage achievements (e.g., 73/100 kards collected).
[REDACTED]
Section titled “[REDACTED]”Stores player submissions for custom series after prestiging Almighties.
_id(ObjectId): Unique submission ID.submitterUserID(String): The player who submitted.guildID(String): The server this was submitted on.status(String): “pending”, “approved”, “rejected”.submittedTimestamp(Date): When the submission was made.reviewedTimestamp(Date, default:null): When an admin reviewed it.adminNotes(String, default:null): Reason for rejection, if any.customSeriesName(String): The proposed series name.customSeriesDescription(String): Thematic description.customKards(Array of Objects): The 25 kard definitions (9 × 1-star, 7 × 2-star, 5 × 3-star, 3 × 4-star, 1 × 5-star).kardName(String): The kard’s name.kardDescription(String): Flavor text.kardRarity(Number): 1–5.kardModifier(String, default:null): Natural modifier (3–5 per custom series).kardImagePath(String): Filepath to the uploaded image on disk. Images are stored as files on the server’s filesystem (e.g.,/assets/custom/{submissionID}/{kardName}.png), not in MongoDB. When the bot receives an image upload during/slkards custom, it saves the file to disk and stores only the filepath here. On approval, images are moved to the main kard image directory alongside base kard images. This keeps MongoDB documents small and avoids the ~100KB+ per image overhead of Base64 encoding.
customModifiers(Array of Objects): 3–5 modifier definitions with themed visuals.kardName(String): Which kard has this modifier.modifier(String): The modifier type.
[REDACTED]
Section titled “[REDACTED]”[REDACTED]
Section titled “[REDACTED]”Synced with player_stats: This collection mirrors the categories in player_stats but at the server aggregate level. Where player_stats tracks an individual’s battles won, guild_stats tracks total battles played on the server. Updated in the same code path as player_stats (increment both atomically).
[Shipped] — backed by repos/guild_stats.py.
Stores aggregate stats for each individual server. Queried for /stats server and server leaderboards.
guildID(String): Unique ID (Discord Server ID).serverLifespan(Number): Hours since SLKARDS was installed.lifetimePlayers(Number): Total players ever registered.activePlayers(Number): Players active in the last 30 days.commandsUsed(Number): Total commands used server-wide.spawns(Object):totalKardsSpawned(Number)seriesSpawned(Array of Objects):[{ seriesName: "Spectral", count: N }, ...]raritiesSpawned(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }modifiersSpawned(Object):{ "animated": N, "negative": N, "polychrome": N, "glitched": N, "pixelated": N, "sketched": N }uniqueKardsFound(Array of Numbers): List ofkardIDs found for the first time on this server.minigamesSpawned(Array of Objects):[{ minigame: "wordWarp", count: N }, ...]
battles(Array of Objects): Same deduplicated structure asplayer_stats.battles.battleType(String):"casualRandom", "casualSet", "ranked"played(Number): How many battles the player has played.attacksUsed(Object): How much each attack has been used (i.e.,{light: N, normal: N, critical: N, superEffective: N, notEffective: N })skillsUsed(Object): How much each skill has been used (i.e.,{invertedMirror: N, blackout: N, ...})gambitsActivated(Object): How much each gambit has been activated (i.e.,{singularity: N, jackpot: N, ...})evolutionsTriggered(Number): How many evolutions were triggered.
trades(Object):tradesCompleted(Number)giftsCompleted(Number)kardsTraded(Object):totalKards(Number)seriesTraded(Array of Objects):[{ seriesName: "Spectral", count: N }, ...]raritiesTraded(Object):{ "1star": N, ... }modifiersTraded(Object):{ "animated": N, ... }
gp(Object):totalGPDistributed(Number)gpPurchases(Object): Aggregate of all GP spending on this server.wishlistUpgrades(Number)marketplaceUpgrades(Number)binderUpgrades(Number)packDiscounts(Number)limitbreakerUnlocks(Number)customEmojis(Number)battleNarrators(Number)titles(Number)footnotes(Number)futureDiaries(Number)juggernautSuits(Number)philosopherStones(Number)warpedPocketWatches(Number)zenoDiaries(Number)counterPurchases(Number)
koins(Object):totalKoinsGenerated(Number)koinSources(Object):{ duplicateSales: N, prestige: N, dailyCommand: N, events: N, achievements: N, allianceWages: N, ngPlusBonuses: N, marketplace: N }totalKoinsSpent(Number)koinSinks(Object):{ packs: N, artificials: N, limitbreakers: N, marketplace: N, allianceCreation: N, allianceDonations: N }
rankedSeasons(Object):seasonsCompleted(Number): Total ranked seasons that have concluded on this server.totalSeasonRewardsDistributed(Object):{ koins: N, gp: N }
prestige(Object):seriesPrestiged(Array of Objects):[{ seriesName: "Spectral", count: N }, ...]customSeriesCreated(Number)ngPlusCyclesCompleted(Number): Total NG+ cycles completed on this server.
wipes(Object):totalWipes(Number)kardsWiped(Object):totalKardsLost(Number)raritiesWiped(Object):{ "1star": N, ... }modifiersWiped(Object):{ "animated": N, ... }artificialsWiped(Number)
wipeSurvivors(Object):{ "1wipe": N, ..., "10plus": N }juggernautSuitsUsed(Number): Total kards protected by Juggernaut Suits across all wipes.
artificials(Object):artificialRaritiesApplied(Number)artificialModifiersApplied(Number)limitbreakerRaritiesApplied(Number)limitbreakerModifiersApplied(Number)evolutionsPrepared(Number)
events(Object):competitionEventsHeld(Number)taskEventsHeld(Number)coopEventsHeld(Number)totalEventKoinsDistributed(Number)totalEventGPDistributed(Number)
duplicates(Object):totalSold(Number)soldByRarity(Object):{ "1star": N, ... }
packs(Object):packsOpened(Number)kardsFromPacks(Object):totalKards(Number)byRarity(Object):{ "1star": N, ... }
marketplace(Object):salesCreated(Number)salesCompleted(Number)auctionsCreated(Number)auctionsCompleted(Number)counterOffersMade(Number)counterOffersAccepted(Number)totalVolume(Number): Total koin value of all marketplace transactions.totalFeesCollected(Number)
alliances(Object):alliancesCreated(Number)alliancesDeleted(Number)totalLevelsGained(Number)totalWagesPaid(Number)totalTaxesCollected(Number)daysWithUnpaidWages(Number)vaultDonations(Number)raidsInitiated(Number)raidsCompleted(Number)raidsByBoss(Array of Objects):[{ bossID: "EFFIGY", count: N }, ...]coalitionRaidsInitiated(Number)coalitionRaidsCompleted(Number)coalitionRaidsByBoss(Array of Objects):[{ bossID: "PRIMORDIAL_1", count: N }, ...]avasPlayed(Number)avasWon(Number)avasLost(Number)avaBattlesPlayed(Number)avaColosseumTournaments(Number)shopPurchases(Object):treasuryExpansions(Number)vaultExpansions(Number)vaultDonationLimits(Number)taxExemptions(Number)channelExpansions(Number)exclusiveCommands(Number)memberCaps(Number)emblems(Number)titles(Number)boons(Object):koin(Array of Numbers): Purchases per level [L1, L2, L3, L4, L5].xp(Array of Numbers)luck(Array of Numbers)raid(Array of Numbers)donation(Array of Numbers)
[REDACTED]
Section titled “[REDACTED]”[REDACTED]
Section titled “[REDACTED]”Tracks individual users with the Premium Personal Pass (PPP). Cross-server — one document per user regardless of how many servers they’re on.
userID(String): Unique ID (Discord User ID).equippedPremiumTitle(String, default:null): Currently equipped premium title (if any).equippedPremiumNarrator(String, default:null): Currently equipped premium narrator.equippedPremiumEmoji(String, default:null): Currently equipped premium emoji set.equippedPremiumFootnote(String, default:null): Currently equipped premium footnote.
[REDACTED]
Section titled “[REDACTED]”Tracks servers with the Premium Server Upgrade (PSU).
guildID(String): Unique ID (Discord Server ID).psuStatus(String): “active” or “lapsed”.psuStartTimestamp(Date): When the subscription began.psuExpiryTimestamp(Date): When the current subscription period ends.adminUserIDs(Array of Strings): Users who get priority support.enabledFeatures(Object): Placeholder flags for premium server features.casinoEnabled(Boolean, default:false): Conceptual — future feature.arcadeEnabled(Boolean, default:false): Conceptual — future feature.stadiumEnabled(Boolean, default:false): Conceptual — future feature.questsEnabled(Boolean, default:false): Conceptual — future feature.extraMinigamesEnabled(Boolean, default:false): Conceptual — future feature.
bonusKoinsPercent(Number, default: 0): Bonus koins percentage for active members.bonusGPPercent(Number, default: 0): Bonus GP percentage for active members.lastBackup(Date, default:null): Timestamp of last weekly data backup.
[REDACTED]
Section titled “[REDACTED]”Why not put everything in Redis? Redis stores all data in RAM, which is expensive and volatile — if Redis crashes or restarts, anything not persisted to disk is lost. This is fine for battles and spawns (short-lived, recreatable) but dangerous for alliances (months of accumulated data), marketplace listings (real koin value at stake), and events (server-wide progress). MongoDB gives you durability (data survives crashes), larger storage capacity (disk is cheap, RAM is not), and richer querying (aggregation pipelines for marketplace browsing, complex alliance vault queries). The rule of thumb: Redis for data you can afford to lose; MongoDB for data you can’t.
Active-to-log pipeline: When an active state concludes (battle finishes, trade completes, event ends, etc.), the bot performs a two-step archive:
- Extract and write to the appropriate
logs_*collection — pull the relevant summary data from the active document and insert a log entry. Log entries are slimmer than active state (they omit turn-by-turn minutiae and only keep outcome data needed for stats, disputes, and audit trails).- Delete the active document (or let Redis TTL expire it). For Redis-stored state (battles, spawns), step 1 happens on completion; TTL handles cleanup of abandoned sessions.
If you need full active state preservation for dispute resolution (e.g., a player contests a battle outcome), add an optional
fullStateSnapshot(Object) field to the log entry that stores the complete active document. Enable this selectively (e.g., only for ranked battles, only for AvA) to avoid bloating logs.
These collections store temporary data for game mechanics currently in progress. Documents are created and destroyed frequently. Battles and spawns are stored in Redis; everything else is in MongoDB (see Section 8 for Redis details).
[REDACTED]
Section titled “[REDACTED]”Stores the full state for player-formed groups. This is a long-lived document (persists until the alliance is deleted).
_id(ObjectId): Unique ID for the alliance.guildID(String): The server this alliance exists on.createdTimestamp(Date): When the alliance was created.profile(Object): Visual and descriptive identity.allianceName(String): Unique per guild.allianceDescription(String)allianceEmblemID(String): Reference to static emblem asset.allianceTitleID(String): Reference to static title.lastRenamed(Date): Enforces the 14-day rename cooldown.lastDescriptionEdit(Date): Enforces the 1-day cooldown.lastEmblemChange(Date): Enforces the 1-day cooldown.
settings(Object): Leader-configured options.allianceType(String): “Social”, “Raids”, or “AvA”.joinMode(String, default: “invite_only”): “open”, “invite_only”, or “closed”. Controls how new members can join the alliance. Open allows direct joins, invite-only requires an invite or approved request, closed disables all new joins.maturityFilter(Boolean): 18+ filter.joinRequirements(Object):minRank(String): Minimum ranked tier to join.weeklyKardDonation(Number): Required kard donations per week.weeklyKoinDonation(Number, default: 0): Required koin donations per week.
roles(Object):leaderUserID(String)coLeaderUserIDs(Array of Strings)officerUserIDs(Array of Strings)memberUserIDs(Array of Strings): Full roster of all members (includes leader/co-leaders/officers).
memberContracts(Object): Keyed by UserID. Employment data per member.[userID](Object): (Square bracket notation means the actual Discord user ID string is used as the key. E.g.,"298371625481": { weeklyWage: 500, ... }. This is a MongoDB pattern for dynamic keys — it allows O(1) lookup of a specific member’s contract viamemberContracts.298371625481without scanning an array.)weeklyWage(Number): Koins paid per week.signOnBonus(Number, default: 0): One-time koins paid when contract was offered.incomeTaxRate(Number): Tax rate applied to this member’s wages (from alliance tax brackets).contractStatus(String): “active”, “pending”, “terminated”.lastWagePaid(Date)joinedTimestamp(Date)weeklyDonations(Object): Tracks current week’s contributions.kardsDonated(Number)koinsDonated(Number)weekStartTimestamp(Date): Reset point for weekly tracking.
pendingInvites(Array of Strings): UserIDs currently invited by the alliance to join.pendingJoinRequests(Array of Objects): Players who requested to join (via/alliance-slkards join).userID(String)requestTimestamp(Date)message(String, default:null): Optional message from the requesting player.
economy(Object):treasuryBalance(Number)treasuryCapacity(Number): Max treasury size (starts at 50,000, increases by +25,000 per Treasury Expansion upgrade, max 175,000).taxExemptionLevel(Number): Shop upgrade level (0–5).wageStatus(String): “active” or “frozen” (if treasury can’t cover payroll).
leveling(Object):currentXP(Number)currentLevel(Number)
vault(Object):capacity(Number): Max unique kards the vault can hold (starts at 50, increases with Vault Expansion upgrades).donationDailyLimit(Number, default: 3): Max kards a single member can donate per day. Increases with Vault Donation Limit upgrades (+2 per purchase, max 9).kards(Array of Objects): The shared inventory.kardID(Number): Reference tostatic_kards.donationCount(Number): How many duplicates have been donated for this kard.donorUserIDs(Array of Strings): Who contributed.statsBonus(Object): Raid stat buffs from donation milestones.hpMultiplier(Number, default: 1.0)atkMultiplier(Number, default: 1.0)
dex(Object): Alliance Dex tracking — series completion in the vault.completedSeries(Array of Strings): Series names fully collected.seriesProgress(Array of Objects):[{ seriesName: "Spectral", owned: 18, total: 25 }, ...]completionBonuses(Array of Objects): Rewards earned from completing series in the Dex.seriesName(String)cosmetic(String): Unlocked cosmetic.koinsAwarded(Number)gpAwarded(Number)
unlocks(Object):perks(Array of Strings): IDs of purchased shop upgrades (e.g., “VAULT_EXPANSION_1”, “TAX_EXEMPTION_2”).boons(Array of Objects): Active temporary buffs.boonID(String): “koin”, “xp”, “luck”, “raid”, “donation”.level(Number): Boon tier (1–5).activatedTimestamp(Date)expiryTimestamp(Date)
cosmetics(Object):unlockedEmblems(Array of Strings)unlockedTitles(Array of Strings)
channels(Object): Discord channel IDs for the alliance’s persistent channels.rulesChannelID(String)recruitmentChannelID(String)officerChannelID(String)membersChannelID(String)donationChannelID(String)resultsChannelID(String)archiveChannelID(String)raidTacticsChannelID(String, default:null): Unlocked via shop.avaTacticsChannelID(String, default:null): Unlocked via shop.suggestionsChannelID(String, default:null): Unlocked via shop.
shopSuggestions(Array of Objects): Pending purchase suggestions from members/officers. Posted to the officer channel for leadership review. Cleared when a leader/co-leader acts on them (approve and purchase, or dismiss).suggestedBy(String): UserID of the member who suggested the purchase.itemID(String): The shop item being suggested (e.g., “VAULT_EXPANSION_2”, “BOON_KOIN_3”).suggestedTimestamp(Date)message(String, default:null): Optional reason from the suggesting member.
stats(Object):avaELO(Number, default: 1000)avaRank(String): “Bronze”, “Silver”, “Gold”, “Platinum”, “Diamond”, “Master”, “Champion”, “Legendary”.avaWins(Number)avaLosses(Number)raidsCompleted(Number)raidsByBoss(Array of Objects):[{ bossID: "EFFIGY", completions: N }, ...]
[REDACTED]
Section titled “[REDACTED]”Sandboxing: This document is a complete snapshot of all battle-relevant kard data at battle start. Nothing in this document writes back to player_binders on battle conclusion. All mutable state (series changes from Swap Space, evolution buffs, GAMBIT effects like Error 0x12B swapping kards between players, Metamorphosis changing modifiers) exists only in the battle deck arrays and is discarded when the battle ends. The only post-battle writes go to: player_core (ELO, GP), player_stats (battle counters), and logs_battles (outcome)..
[Designed] — ships with 0.4.
Stores the state of an ongoing battle. Stored in Redis (see Section 8). On battle end, archive to logs_battles and delete.
_id(ObjectId/String): Unique battle ID.guildID(String): The server the battle is on.stakes(String): “casual” or “ranked”.format(String): “random” or “set”. Ranked is always “set”.status(String): “pending_invite”, “deck_selection”, “active”, “finished”.startedTimestamp(Date): When the battle began.turnNumber(Number): Current turn (starts at 1).avaContext(Object, default:null): Set only if this battle is part of an AvA war.avaMatchID(ObjectId): Reference toactive_ava_battles.alliance1ID(ObjectId)alliance2ID(ObjectId)matchupIndex(Number): Index in the AvA matchups array for result reporting.
player1(Object):userID(String)deck(Array of Objects): The 6 kards with full battle state. All fields below are snapshots — they do NOT persist back toplayer_binders.kardInstanceID(ObjectId): Reference toplayer_binders(for post-battle stat attribution only).kardID(Number): Static kard ID.seriesName(String): The kard’s current battle series. Initialized from static data. Can change via Swap Space SKILL (and evolved Swap Space can change opponent’s kards too). Never written back toplayer_binders.originalSeriesName(String): The kard’s natural series at battle start. Immutable reference for type chart lookups before Swap Space.totalRarity(Number): Sum of natural + artificial + limitbreaker rarities. Snapshot at battle start.activeModifier(String): The modifier in effect (priority: natural > limitbreaker > artificial). Can change via GAMBIT Metamorphosis. Never written back.originalModifier(String): The modifier at battle start. Immutable reference.isArtificialModifier(Boolean):trueif using SEMI- modifier (weakened skills).isEvolutionReady(Boolean): Snapshot fromplayer_binders.hasEvolved(Boolean, default:false):trueif evolution activated this battle. Battle-local only.damageMultiplier(Number, default: 1.0): Multiplier for all attack damage. Set to 1.2 on evolution (+20%). Affected by Jackpot GAMBIT (2.0 for 3 turns).maxHP(Number): Calculated from rarity at battle start (8400–11750 range). Increased by 50% on evolution. Never written back.currentHP(Number): Current hit points. Heals 50% of current HP on evolution.isDefeated(Boolean, default:false): Can be flipped back tofalseby Bit Crush GAMBIT (revive).belongsToPlayer(Number, default: 1 or 2): Tracks original ownership. Can change via Error 0x12B GAMBIT (swaps dormant kards between players). Used to correctly attribute stats post-battle.skillCondition(Object): Hidden SKILL activation requirement. Randomized at battle start, persists for the entire battle (once discovered, re-usable).conditionType(String): One of:"3_turns_active"— kard must be active for 3 consecutive turns."light_attack_hit"— kard must land a light attack."not_effective_hit"— kard must land a not-effective (0.5x) attack."critical_hit_received"— kard must be hit by a critical attack."super_effective_hit"— kard must be hit by a super-effective (2x) attack."20_percent_random"— 20% chance of activation each round."last_kard"— kard must be the last remaining in the player’s deck.
conditionMet(Boolean, default:false): Flips totruewhen condition is satisfied. Resets tofalseafter SKILL is used (condition must be met again).turnsActive(Number, default: 0): Counter for “3_turns_active” condition. Resets on switch-out.skillUsesRemaining(Number, default:null): Only set for “last_kard” condition. Equals the opponent’s remaining kard count when triggered. Decrements on each SKILL use. When 0, SKILL is exhausted.
activeEffects(Array of Objects): Status effects currently applied to this kard. All are battle-local and discarded on battle end.effectName(String): One of:"blackout"— blocks opponent from switching (3 turns, 4 if evolved)."prismaticTrap"— next switch deals 1/5 max HP damage to outgoing kard (1/3 if evolved)."ditherBoost"— light accuracy → 0, normal or critical accuracy boosted (5 turns). Evolved: light accuracy → critical."dotMatrix"— shield absorbs ATTACKs (3 turns, 4 if evolved). Does NOT block SKILLs or GAMBITs."freezeFrame"— player is frozen and cannot act (3 turns)."singularityCollecting"— collecting attacks for 3 turns (Singularity GAMBIT)."jackpot"— 2x damage for 3 turns (Jackpot GAMBIT)."vitalSketchHOT"— healing over time from evolved Vital Sketch (3 turns, heals max HP / 3 per turn).
turnsRemaining(Number)effectData(Object): Effect-specific data.- For
singularityCollecting:{ accumulatedDamage: N }— total damage collected. - For
ditherBoost:{ boostedType: "normal" or "critical" }— which accuracy was raised. - For
dotMatrix:{ shieldHP: N }— remaining shield HP (if using HP-based shield variant). - For
prismaticTrap:{ targetPlayer: 1 or 2 }— which player’s switch triggers the damage.
- For
activeKardIndex(Number): Index of the currently fielded kard (0–5).lastActionChoice(String, default:null): “ATTACK_LIGHT”, “ATTACK_NORMAL”, “ATTACK_CRITICAL”, “SKILL”, “SWITCH”, “FORFEIT”, “CHECK”.selectedSkillName(String, default:null): Which of the active kard’s two modifier SKILLs the player chose to use. Required whenlastActionChoiceis “SKILL”. Values correspond to SKILL names: “INVERTED_MIRROR”, “BLACKOUT”, “GAMBLERS_FALLACY”, “PRISMATIC_TRAP”, “DITHER_BOOST”, “DOT_MATRIX”, “SWAP_SPACE”, “DISTORTION_SLASH”, “VITAL_SKETCH”, “KARD_TRACE”, “FAST_FORWARD”, “AFTERIMAGE”.lastSwitchTarget(Number, default:null): Index of the kard being switched to.lastSkillTarget(Object, default:null): For targeted SKILLs (Vital Sketch, Kard Trace, Swap Space).targetKardIndex(Number): Which kard the SKILL targets.targetPlayer(Number, default:null): 1 or 2 — for evolved Swap Space (can target opponent’s kard).
evolutionUsed(Boolean, default:false): Only one evolution per player per battle.gambitChance(Number, default: 0.05): Per-round GAMBIT activation chance. 0.05 (5%) for natural modifiers, 0.10 (10%) for artificial/SEMI- modifiers.
player2(Object): Same structure asplayer1.turnLog(Array of Objects): Turn-by-turn history for battle replay/review.turn(Number)p1Action(String)p2Action(String)p1Result(Object):{ damage: N, hitType: "light"/"normal"/"critical"/"miss", superEffective: Boolean, notEffective: Boolean }p2Result(Object): Same structure.events(Array of Strings): Notable events (“GAMBIT_SINGULARITY”, “GAMBIT_JACKPOT”, “GAMBIT_BIT_CRUSH”, “GAMBIT_ERROR_0X12B”, “GAMBIT_METAMORPHOSIS”, “GAMBIT_FREEZE_FRAME”, “EVOLUTION_TRIGGERED”, “SKILL_INVERTED_MIRROR”, “SKILL_BLACKOUT”, “SKILL_GAMBLERS_FALLACY”, “SKILL_PRISMATIC_TRAP”, “SKILL_DITHER_BOOST”, “SKILL_DOT_MATRIX”, “SKILL_SWAP_SPACE”, “SKILL_DISTORTION_SLASH”, “SKILL_VITAL_SKETCH”, “SKILL_KARD_TRACE”, “SKILL_FAST_FORWARD”, “SKILL_AFTERIMAGE”, “SUPER_EFFECTIVE”, “NOT_EFFECTIVE”, “KARD_DEFEATED”, “KARD_REVIVED”, etc.).
[REDACTED]
Section titled “[REDACTED]”Stores the state of an active single-alliance PvE raid (Colossal Kard).
_id(ObjectId): Unique Raid ID.guildID(String)allianceID(ObjectId): Reference toactive_alliances.bossData(Object):bossID(String): Reference tostatic_raid_bosses.bossName(String)currentHP(Number)maxHP(Number)mimicSeries(String): The randomly chosen series type advantage for this raid event.spawnTimestamp(Date)endTimestamp(Date): 1 week from spawn.
memberParticipation(Object): Keyed by UserID.[userID](Object):attacksUsed(Number): Out of 5 total attacks for this raid.totalDamageDealt(Number)lastAttackTimestamp(Date)usedVaultKardIDs(Array of Numbers): Vault kard IDs this player has already used in previous raid attempts. Prevents reuse — when selecting a deck for a new attempt, anykardIDin this array is blocked for this player. Updated after each attack concludes. Maximum of 30 entries (6 kards × 5 attempts).
channelIDs(Object):announcementChannelID(String)battleChannelID(String)
status(String): “active”, “defeated”, “expired”.
[REDACTED]
Section titled “[REDACTED]”Stores the state of a server-wide Primordial Boss event (multi-alliance).
_id(ObjectId)guildID(String)bossID(String): Reference tostatic_raid_bosses(Primordial type).bossName(String)currentHP(Number)maxHP(Number): Scaled by number of participating alliances.status(String): “active”, “defeated”, “expired”.spawnTimestamp(Date)endTimestamp(Date)allianceDamage(Object): Keyed by AllianceID. Used to calculate reward splits.[allianceID](Object):totalDamage(Number)memberCount(Number): Members who participated.
playerParticipation(Object): Keyed by UserID. Max 1 attack per player per day.[userID](Object):allianceID(ObjectId): Which alliance they’re fighting for.damageDealt(Number)lastAttackDate(Date): For enforcing 1-attack-per-day limit.
[REDACTED]
Section titled “[REDACTED]”Stores the state of a specific Alliance vs. Alliance war.
_id(ObjectId): Unique AvA ID.guildID(String)alliance1ID(ObjectId)alliance2ID(ObjectId)status(String): “queue”, “scheduled”, “check_in”, “active”, “tiebreaker”, “completed”.matchData(Object):alliance1StartELO(Number): ELO at time of matching.alliance2StartELO(Number)matchAverageELO(Number): Calculated MMR for reward logic.
schedule(Object):scheduledDate(Date): The battle day.checkInStart(Date): 15-minute check-in window start.checkInEnd(Date): Check-in window end.battleDeadline(Date): 1 hour after check-in ends.tieBreakerDate(Date, default:null): Next day if tied.
checkIns(Object):alliance1CheckedIn(Array of Strings): UserIDs who checked in.alliance2CheckedIn(Array of Strings): UserIDs who checked in.
channels(Object):categoryID(String)announcementChannelID(String)
matchups(Array of Objects):player1UserID(String): From alliance 1.player2UserID(String): From alliance 2.threadID(String): Discord thread ID for the battle.battleID(ObjectId, default:null): Reference toactive_battlesonce started. Theactive_battlesdocument stores AvA context in itsavaContextfield (avaMatchID, alliance IDs, matchup index), which the battle resolution logic uses to report results back toactive_ava_battleson completion. No extra structural fields are needed inactive_battlesbeyond theavaContextobject — the battle itself plays identically to a ranked set battle.winnerUserID(String, default:null)status(String): “pending”, “checked_in”, “in_progress”, “completed”, “forfeit”.
results(Object, default:null):alliance1Wins(Number)alliance2Wins(Number)winnerAllianceID(ObjectId, default:null)
[REDACTED]
Section titled “[REDACTED]”Stores the state of a “Colosseum” tournament bracket (Premium Servers only).
_id(ObjectId)guildID(String)rankBracket(String): “Bronze”, “Silver”, “Gold”, “Platinum”, “Diamond”, “Legendary”.status(String): “registration”, “active”, “completed”.participants(Array of ObjectIds): Alliance IDs (up to 32).bracket(Array of Array of Objects): Tournament bracket structure.- Each round is an array of matchup objects:
alliance1ID(ObjectId)alliance2ID(ObjectId)avaMatchID(ObjectId, default:null): Reference toactive_ava_battles.winnerAllianceID(ObjectId, default:null)
- Each round is an array of matchup objects:
currentRound(Number): Current round of the tournament (1, 2, 3…).
[REDACTED]
Section titled “[REDACTED]”Tracks currently running and scheduled server-wide events. Only one event per guild may have status: "active" at a time.
_id(ObjectId): Unique event ID.guildID(String): The server this event is on.status(String):"scheduled"(not yet started) or"active"(currently running). On transition from scheduled → active, the bot posts the event start embed inslkards-eventsand activates any modifiers.createdByUserID(String): Admin who created the event.eventType(String): “competition”, “task”, “coop”.eventName(String): Display name of the event.eventDescription(String): The event text.scheduledStartTimestamp(Date, default:null): Set for scheduled events.nullfor events created with/admin-slkards eventcreate(starts immediately). The bot’s scheduler checks this each minute and flipsstatusto"active"when the time arrives.startTimestamp(Date, default:null): When the event actually became active. Set at creation for immediate events; set by the scheduler for scheduled events.endTimestamp(Date): When the event ends. Calculated asstartTimestamp + durationHours. For scheduled events, pre-calculated fromscheduledStartTimestamp + durationHoursand updated when the event activates.modifiers(Object, default:null): Active event modifiers (e.g., 200% spawn rate).spawnRateMultiplier(Number, default: 1.0)koinBonusMultiplier(Number, default: 1.0)gpBonusMultiplier(Number, default: 1.0)
rewards(Object): Reward tiers.competitionTiers(Array of Objects, default:null): For competition events.percentile(Number): Top 1%, 5%, 10%, 25%, 50%.koins(Number)gp(Number)
taskRewards(Object, default:null): For task events.perTask(Object):{ koins: N, gp: N }allTasksBonus(Object):{ koins: N, gp: N }
coopRewards(Object, default:null): For co-op events.participationReward(Object):{ koins: N, gp: N }majorContributorBonus(Object):{ koins: N, gp: N }— for 50%+ contribution.
taskDefinitions(Array of Objects, default:null): For task events — defines each individual task.taskID(String): Unique task identifier within the event.taskDescription(String): What the player must do.taskTarget(Number): Completion threshold (e.g., “Win 5 battles” → 5).
coopGoals(Array of Objects, default:null): For co-op events — can have multiple server-wide goals.goalID(String): Unique goal identifier.goalDescription(String)target(Number): Server-wide target (e.g., 10,000 trades).currentProgress(Number, default: 0): Current server-wide count.isCompleted(Boolean, default:false)
goal(Number, default:null): Simple single-goal target for co-op events. UsecoopGoalsfor multi-goal events.currentProgress(Number, default: 0): Current count for simple single-goal co-op events.participants(Array of Objects): For all event types.userID(String)score(Number): Competitive score / co-op contribution count.tasksCompleted(Array of Objects, default:null): For task events.taskID(String)progress(Number): Current count toward the task target.isCompleted(Boolean)
coopContributions(Object, default:null): For co-op events — per-goal contribution.[goalID](Number): How much this player contributed to each goal.
[REDACTED]
Section titled “[REDACTED]”Stores all active marketplace listings (for-sale today; auctions arrive with the 0.3.5 auction flow per the # TODO 0.3.5 markers in services/store_market.py and cogs/stores/marketplace.py).
_id(ObjectId): Unique listing ID.guildID(String)sellerUserID(String)kardInstanceID(ObjectId): Reference toplayer_binders.kardID(Number): Static kard ID (denormalized for browsing).kardSeries(String): Denormalized for browsing.kardRarity(Number): Total effective rarity (denormalized).kardModifier(String, default:null): Denormalized.listingType(String): “sale” or “auction”.listedTimestamp(Date): When the item was posted.price(Number): Buy-it-now price for “sale” listings.counterOffers(Array of Objects, default:[]): For “sale” listings.offererUserID(String)offerPrice(Number)offerTimestamp(Date)status(String): “pending”, “accepted”, “rejected”, “expired”.
auctionData(Object, default:null): For “auction” listings only.startingPrice(Number): Minimum opening bid.currentBid(Number, default: 0): Highest bid amount.highestBidderUserID(String, default:null)bidHistory(Array of Objects): Full bid trail.bidderUserID(String)bidAmount(Number)bidTimestamp(Date)
auctionEndTimestamp(Date): When the auction closes. Starts at 12 hours.currentTimerSeconds(Number): Remaining timer in seconds. Decreases by 10% per new bid.totalBids(Number): Count of bids placed.
[REDACTED]
Section titled “[REDACTED]”_Stores the state of an in-progress spawn minigame.
_id(ObjectId/String): Unique spawn event ID.guildID(String)channelID(String): The channel the spawn is in.kardID(Number): The kard at stake.kardRarity(Number): Determines which minigames are eligible.minigameType(String): “wordWarp”, “flagGuesser”, “numberHunter”, “luckyDraw”, “redLightGreenLight”, “rockPaperScissors”, “coinSurvival”, “triviaQuestions”, “ultimateShowdown”.status(String): “spawning” (1-min warning), “in_progress”, “concluded”.spawnedTimestamp(Date)minigameState(Object): Dynamic state specific to the minigame type. Examples:- Word Warp:
{ scrambledWord: "SDARLKS", answer: "SLKARDS", scores: { "userID": 2 } } - Number Hunter:
{ secretNumber: 981, guesses: { "userID": 900 }, deadline: Date } - Lucky Draw:
{ registeredPlayers: ["userID1", "userID2", ...] } - Red Light Green Light:
{ currentColor: "green", eliminatedPlayers: [...], survivors: [...] } - Ultimate Showdown:
{ round: 1, minigames: ["wordWarp", "coinSurvival", "triviaQuestions"], scores: { "userID": 1 } }
- Word Warp:
playerPool(Array of Strings): UserIDs currently participating.maxPlayers(Number): Pool cap by rarity (1-star: 4, 2-star: 6, 3-star: 8, 4-star: 8, 5-star: 6).winnerUserID(String, default:null): Set when minigame concludes.
[REDACTED]
Section titled “[REDACTED]”Stores the state of a pending trade between two players.
_id(ObjectId): Unique trade ID.guildID(String)player1UserID(String)player2UserID(String)player1Offer(Array of ObjectIds): Kard instance IDs from their binder (max 10).player2Offer(Array of ObjectIds): Kard instance IDs from their binder (max 10).player1Ready(Boolean, default:false): Set after viewing and confirming.player2Ready(Boolean, default:false)isGift(Boolean, default:false):trueif one side has no kards (unilateral gift).createdTimestamp(Date): Used to auto-cancel after timeout.expiryTimestamp(Date): Auto-cancel deadline.
[REDACTED]
Section titled “[REDACTED]”Relationship to active collections: Each active collection has a corresponding log collection that receives a summary document when the active state concludes. The mapping is:
| Active Collection | Log Collection | What’s Archived |
|---|---|---|
active_battles (Redis) | logs_battles | Winner, loser, ELO change, GP awarded, turn count, decks used. Optionally includes fullStateSnapshot for ranked/AvA battles for dispute resolution. |
active_trades | logs_trades | Both players, kards exchanged, rep changes. |
active_events | logs_events | Event type, top participants, rewards distributed. |
active_market | logs_market | Seller, buyer, kard, final price, fees. One entry per completed sale/auction. |
active_alliance_raids | logs_alliances (actionType: “raid_completed”) | Boss, outcome, damage breakdown. |
active_ava_battles | logs_alliances (actionType: “ava_completed”) | Alliances, matchup results, ELO changes. |
active_spawns (Redis) | No dedicated log | Spawn outcomes are recorded implicitly via player_binders (kard creation) and guild_stats (spawn counters). |
The log entry is always slimmer than the active document — it captures outcomes, not turn-by-turn state. For cases where full state preservation is needed (dispute resolution), the active document is serialized into a
fullStateSnapshotfield on the log entry before deletion.
These collections store a history of events for moderation, stats, and dispute resolution. They are append-only — never edit, only insert.
[REDACTED]
Section titled “[REDACTED]”Records all uses of debug and admin commands.
timestamp(Date)guildID(String)adminUserID(String)commandName(String): e.g., “forcespawn”, “ban”, “givekoins”.parameters(Object): Key-value pairs of command arguments.
[REDACTED]
Section titled “[REDACTED]”Records major alliance actions for audit trails.
timestamp(Date)guildID(String)allianceID(ObjectId)actionType(String): “created”, “deleted”, “member_joined”, “member_kicked”, “member_left”, “leader_transferred”, “settings_changed”, “raid_started”, “ava_started”, “shop_purchase”.actorUserID(String): Who performed the action.targetUserID(String, default:null): Who was affected (if applicable).details(Object, default:null): Additional context.
[REDACTED]
Section titled “[REDACTED]”Records the outcome of all battles (ranked, casual, random, AvA).
timestamp(Date)guildID(String)battleType(String): “ranked”, “casual”, “random”.isAvaBattle(Boolean, default:false):trueif this battle was part of an AvA war.avaMatchID(ObjectId, default:null): Reference toactive_ava_battlesif AvA.winnerUserID(String)loserUserID(String)winnerEloChange(Number)loserEloChange(Number)gpAwarded(Number): GP given to the winner (ranked only).turnCount(Number): How many turns the battle lasted.winnerDeck(Array of Numbers): kardIDs used.loserDeck(Array of Numbers): kardIDs used.evolutionsTriggered(Number, default: 0): How many evolutions occurred.gambitsTriggered(Number, default: 0): How many GAMBITs activated.fullStateSnapshot(Object, default:null): Complete battle state at conclusion. Only stored for ranked and AvA battles — enables dispute resolution by preserving the full turn log, deck states, and all effects. Omitted for casual/random battles to save space.
[REDACTED]
Section titled “[REDACTED]”Records the first time each kard is found on a server.
guildID(String)discoveries(Object): Keyed by kardID.[kardID](Object):userID(String): Who found it first.timestamp(Date): When it was first discovered.
[REDACTED]
Section titled “[REDACTED]”Records the outcome of server events.
timestamp(Date)guildID(String)eventType(String): “competition”, “task”, “coop”.eventName(String)winnersInfo(Array of Objects): Top participants and their rewards.userID(String)rank(Number): Placement.score(Number)rewardKoins(Number)rewardGP(Number)
[REDACTED]
Section titled “[REDACTED]”A ledger of all successful marketplace sales (auctions land with the 0.3.5 auction flow). Receives a slim summary on each completed listing.
timestamp(Date)guildID(String)transactionType(String): “sale”, “auction”, “counter_offer_accepted”.sellerUserID(String)buyerUserID(String)kardID(Number)kardInstanceID(ObjectId)finalPrice(Number)transactionFee(Number): 5% of sale price.
[REDACTED]
Section titled “[REDACTED]”Records what players receive from packs.
timestamp(Date)guildID(String)userID(String)packID(String): Reference tostatic_packs.kardsRevealed(Array of Objects): All kards shown.kardID(Number)rarity(Number)modifier(String, default:null)
kardsChosen(Array of Numbers): kardIDs the player kept.
[REDACTED]
Section titled “[REDACTED]”A receipt for every prestige (and Almighty roll outcome). Powers per-series prestige leaderboards and the Almighty stats fan-out introduced in 0.3.
timestamp(Date)guildID(String)userID(String)seriesName(String): The series prestiged.prestigeCountAfter(Number): That player’s prestige count for the series after this event.almightyAwarded(Number | null): The Almighty kardID rolled, if any.koinsAwarded(Number)
[REDACTED]
Section titled “[REDACTED]”A receipt for every completed trade.
timestamp(Date)guildID(String)player1UserID(String)player2UserID(String)player1KardsGiven(Array of ObjectIds): Kard instance IDs.player2KardsGiven(Array of ObjectIds): Kard instance IDs.wasGift(Boolean):trueif one side gave nothing.player1RepChange(Number): +1, -1, or 0.player2RepChange(Number): +1, -1, or 0.
[REDACTED]
Section titled “[REDACTED]”Records the history of all wipes.
timestamp(Date)guildID(String)wipeNumber(Number): Sequential wipe count for this server.totalKardsSnapped(Number)playersAffected(Number)kardsSnappedByRarity(Object):{ "1star": N, "2star": N, "3star": N, "4star": N, "5star": N }juggernautSuitsUsed(Number): How many kards were protected.
[REDACTED]
Section titled “[REDACTED]”Scope: Redis handles only battles, spawns, cooldowns, leaderboards, and session locks. All other active state (alliances, marketplace, trades, events, raids, AvA) lives in MongoDB for durability.
[REDACTED]
Section titled “[REDACTED]”Key: battle:{battleID}
TTL: 2 hours (auto-cleanup for abandoned battles)
Store the full active_battles document as a Redis hash. Benefits: sub-millisecond reads/writes for every turn action, automatic expiry for stale battles, and no MongoDB write amplification from rapid turn-by-turn updates.
On battle completion, persist the final state to logs_battles in MongoDB and delete the Redis key.
[REDACTED]
Section titled “[REDACTED]”Key: spawn:{guildID}:{channelID}
TTL: 15 minutes
Store the full active_spawns document. Spawns are extremely short-lived (1–10 minutes). Redis TTL handles automatic cleanup of abandoned spawns.
[REDACTED]
Section titled “[REDACTED]”Key patterns:
cooldown:daily:{userID}:{guildID}→ TTL: 24 hourscooldown:secret:{userID}:{guildID}→ TTL: 24 hourscooldown:trade:{userID}:{guildID}→ TTL: 30 seconds (anti-spam)
Check with EXISTS, set with SET ... EX. No MongoDB queries needed for cooldown checks.
[REDACTED]
Section titled “[REDACTED]”Key: spawntimer:{guildID}
Value: Next spawn timestamp.
The bot checks this key to know when to trigger the next spawn. Set after each spawn resolves.
[REDACTED]
Section titled “[REDACTED]”Player leaderboard key patterns:
lb:{guildID}:elo→ Sorted by current season ELO. Source:player_core.currentElo.lb:{guildID}:prestige→ Sorted by total prestige count. Source:player_core.prestigeProgress.totalPrestiges.lb:{guildID}:ngplus→ Sorted by NG+ cycle count. Source:player_core.ngPlus.cycleCount.lb:{guildID}:kards→ Sorted by total kards collected (lifetime). Source:player_stats.collection.totalKardsCollected.lb:{guildID}:koins→ Sorted by total koins received (lifetime). Source:player_stats.koins.totalKoinsReceived.lb:{guildID}:minigames→ Sorted by total minigames won. Source:player_stats.minigames.totalMinigamesWon.
Alliance leaderboard key pattern:
lb:{guildID}:avaWins→ Sorted by alliance AvA wins. Source:active_alliances.stats.avaWins.
Populate from MongoDB on schedule (daily for ELO percentiles, every 6 hours for others). Serve leaderboard queries directly from Redis using ZREVRANGE (top N) and ZRANK (player’s position). The slkards-leaderboards channel embeds display only the top 10 per category as a snapshot; the /slkards leaderboard command serves paginated full rankings from these same sorted sets.
[REDACTED]
Section titled “[REDACTED]”Key: raid_attack:{raidID}:{userID}
TTL: 30 minutes (auto-cleanup for abandoned attacks)
Stores the ephemeral state of a player’s attack turn against a raid or coalition boss. Created when a player initiates an attack, destroyed when the attack concludes. This is deliberately separate from active_battles — raids are PvE with fundamentally different mechanics: single player vs. one AI-controlled boss kard, persistent boss HP across sessions, 5 daily attacks per player, and no ELO/GP stakes.
raidID(ObjectId): Reference toactive_alliance_raidsoractive_coalition_raids.raidType(String): “alliance” or “coalition”.userID(String): The attacking player.guildID(String)playerDeck(Array of Objects): Up to 6 kards the player chose from the alliance vault for this attack. All fields are battle-local and do not write back to the vault orplayer_binders. Vault kards receive donation milestone stat bonuses (seeactive_alliances.vault.kards[].statsBonus). Aligned withactive_battlesplayer deck structure, with raid-specific differences noted below.vaultKardID(Number): ThekardIDfrom the alliance vault’s kards array. Used to enforce the per-player blocking rule (kards used in previous attempts cannot be reused).kardID(Number): Static kard ID, same asvaultKardID(included for consistency with PvP deck structure).seriesName(String): Snapshot from static data. Immutable during raids (no Swap Space in PvE).originalSeriesName(String): Immutable reference for type chart lookups. Same asseriesNamein raids (included for code reuse with PvP battle logic).totalRarity(Number): Rarity fromstatic_kards. Vault kards do not have artificial/limitbreaker rarities — they use their base rarity only.activeModifier(String, default:null): The kard’s natural modifier fromstatic_kards. Vault kards do not have artificial/limitbreaker modifiers. Can change via GAMBIT Metamorphosis (battle-local only).originalModifier(String): Immutable reference. Same asactiveModifierat raid start.isArtificialModifier(Boolean): Alwaysfalsefor vault kards (no artificial system in vault).isEvolutionReady(Boolean): Alwaysfalsefor vault kards (evolution requires Dr. Higginbotham’s lab prep, which is player-binder-specific).hasEvolved(Boolean, default:false): Alwaysfalsein raids (evolution is disabled for vault kards).damageMultiplier(Number, default: 1.0): Initialized from vault kard’sstatsBonus.atkMultiplier(donation milestone buff). Modified by Jackpot GAMBIT (2.0 for 3 turns) if triggered.maxHP(Number): Calculated from base rarity HP table, then multiplied by vault kard’sstatsBonus.hpMultiplier(donation milestone buff).currentHP(Number): Current hit points.isDefeated(Boolean, default:false): Can be flipped back by Bit Crush GAMBIT.belongsToPlayer(Number, default: 1): Always 1 in raids (no swapping with boss). Included for code reuse.skillCondition(Object): Same structure as PvPactive_battles—conditionType,conditionMet,turnsActive,skillUsesRemaining. Randomized at raid attack start.activeEffects(Array of Objects): Same structure as PvPactive_battles—effectName,turnsRemaining,effectData. All effects are attack-local and discarded on attack end.
activeKardIndex(Number): Index of the currently fielded kard (0–5).lastActionChoice(String, default:null): Same values as PvP battles.selectedSkillName(String, default:null): Which SKILL the player chose to use.lastSwitchTarget(Number, default:null): Index of the kard being switched to.lastSkillTarget(Object, default:null): Same structure as PvP battles.evolutionUsed(Boolean, default:false): Only one evolution per attack.gambitChance(Number, default: 0.05): Same rules as PvP.bossSnapshot(Object): State of the boss at attack start.bossID(String): Reference tostatic_raid_bosses.bossName(String)mimicSeries(String): The boss’s series type for type chart interactions.currentHP(Number): Snapshotted fromactive_alliance_raids.bossData.currentHPat attack start.maxHP(Number)bossModifier(String): The boss’s modifier (determines boss SKILL/GAMBIT behavior).activeEffects(Array of Objects): Effects currently on the boss.
turnNumber(Number): Current turn (starts at 1).turnLog(Array of Objects): Same structure as PvPturnLog.totalDamageDealt(Number, default: 0): Running total of damage dealt to the boss this attack.status(String): “active”, “player_defeated”, “completed”.
On attack conclusion: write totalDamageDealt back to active_alliance_raids.memberParticipation[userID] and subtract from bossData.currentHP atomically. Delete the Redis key. If bossData.currentHP reaches 0, trigger raid completion logic.
[REDACTED]
Section titled “[REDACTED]”Key: event_notify:{userID}:{guildID}:{eventID}
TTL: 1 hour
Rate-limits competition event tier-change DMs to one per player per hour per event. When a player’s tier changes, check if this key exists. If it does, queue the notification (overwriting any previously queued message) and let it fire when the TTL expires. If it doesn’t exist, send the DM immediately and set the key. This prevents notification fatigue during fast-moving competition events where placements fluctuate rapidly.
[REDACTED]
Section titled “[REDACTED]”Key patterns:
lock:trade:{userID}→ TTL: 5 minutes. Prevents double-trade.lock:market:{kardInstanceID}→ TTL: 30 seconds. Prevents double-purchase.lock:pack:{userID}:{guildID}→ TTL: 10 seconds. Prevents double-opening.lock:raid_attack:{userID}→ TTL: 30 minutes. Prevents starting a second raid attack while one is active.
Use SET ... NX EX for atomic lock acquisition.
[REDACTED]
Section titled “[REDACTED]”[REDACTED]
Section titled “[REDACTED]”player_core:
{ userID: 1, guildID: 1 }— Unique compound index. Primary lookup for every player operation.{ guildID: 1, currentElo: -1 }— Ranked leaderboard queries per server.{ guildID: 1, "ngPlus.cycleCount": -1 }— NG+ leaderboard.{ allianceID: 1 }— Find all members of an alliance.
player_binders:
{ ownerUserID: 1, guildID: 1 }— Fetch a player’s full binder.{ ownerUserID: 1, guildID: 1, kardID: 1 }— Check if player owns a specific kard.{ guildID: 1, kardID: 1 }— Count how many instances of a kard exist on a server.{ isInMarket: 1, guildID: 1 }— Find all marketplace-listed kards.{ isInLab: 1, "labJob.completionTimestamp": 1 }— Poll for completed lab jobs.
player_stats:
{ userID: 1, guildID: 1 }— Unique compound index. Lookup for stats queries.{ guildID: 1, "collection.totalKardsCollected": -1 }— Leaderboard refresh: total kards collected.{ guildID: 1, "koins.totalKoinsReceived": -1 }— Leaderboard refresh: total koins received.{ guildID: 1, "minigames.totalMinigamesWon": -1 }— Leaderboard refresh: minigames won.
player_achievements:
{ userID: 1, guildID: 1, achievementID: 1 }— Unique compound index. Check if player has achievement.{ userID: 1, guildID: 1 }— Fetch all achievements for a player. active_alliances:{ guildID: 1 }— List alliances on a server.{ guildID: 1, "profile.allianceName": 1 }— Unique compound index. No duplicate names per server.{ guildID: 1, "stats.avaWins": -1 }— Leaderboard refresh: alliance AvA wins.{ "roles.memberUserIDs": 1 }— Find alliance by member.
active_market:
{ guildID: 1, listingType: 1, kardID: 1 }— Browse marketplace by kard.{ guildID: 1, sellerUserID: 1 }— Find a player’s active listings.{ "auctionData.auctionEndTimestamp": 1 }— Find expiring auctions. Set TTL index if desired.
Log collections (all):
{ guildID: 1, timestamp: -1 }— Primary query pattern for all logs.{ timestamp: 1 }— TTL index to auto-archive old logs (optional, e.g., 90 days).
guild_stats:
{ guildID: 1 }— Unique index. One document per server.
configs_guilds:
{ guildID: 1 }— Unique index. One document per server.{ nextSpawn: 1 }— Poll for upcoming spawns across servers.
[REDACTED]
Section titled “[REDACTED]”Wishlist notifications (DM alerts when a wished-for kard spawns or appears on the marketplace) do not require persistent storage. When a spawn or marketplace listing occurs, the bot queries player_core.kardWishlist for matching kardIDs across relevant players and fires Discord DMs in real-time. No notification queue or history is stored — if the user is offline, the DM sits in their Discord inbox natively.
If notification delivery becomes unreliable at scale, add a lightweight Redis list:
- Key:
notifications:{userID}— List of pending notification payloads. - TTL: 24 hours (auto-expire undelivered notifications).
- Pop on next user interaction.
[REDACTED]
Section titled “[REDACTED]”| Pattern | Type | TTL | Purpose |
|---|---|---|---|
battle:{id} | Hash | 2h | Active battle state |
spawn:{guildID}:{channelID} | Hash | 15m | Active spawn/minigame state |
cooldown:{type}:{userID}:{guildID} | String | Varies | Command cooldowns |
spawntimer:{guildID} | String | None | Next spawn timestamp |
lb:{guildID}:{category} | Sorted Set | None | Leaderboard cache |
lock:{type}:{id} | String | 5-30s | Transaction locks |
event_notify:{userID}:{guildID}:{eventID} | String | 1h | Event DM rate-limit |
See also: Bot Architecture · Commands · Roadmap