44 KiB
AGENTS.md — ShaiBot Repository Guide for AI Agents
Repository: ShaiBot
Runtime: Node.js / CommonJS / Discord.js / MongoDB
Purpose of this file: Give AI coding agents enough architectural, state, collection, concurrency, economy, progression, and interaction context to modify ShaiBot safely without rediscovering the same invariants on every task.Critical rule: The current repository code is always the source of truth. This document is a map and contract, not a substitute for reading the files involved in a change. If this file and code disagree, inspect the relevant implementation and update this guide after the code is settled.
1. What ShaiBot Is
ShaiBot is a modular Discord bot built around a fictional Cookie economy and a set of community/game systems.
Major feature families include:
- Cookie economy
/cookiescollection, giving, stealing, balance checks, and leaderboard- Crumb Saucer game hub
- Slots
- Blackjack
- Roulette
- Vault Heist
- Bankruptcy/starter recovery
- Multiplayer Poker
- Player profiles
- Achievements
- Guild-unique/global badges
- Activity/day/time tracking
- Crumble Strike cooperative boss battles
- Raid planning
- Records/stat presentation
- Miscellaneous social/community commands
The project strongly prefers shared utilities in utils/ with thin command/button/select-menu wrappers.
Do not redesign unrelated systems while implementing a focused feature.
2. Repository Layout
Typical high-level structure:
ShaiBot/
├── assets/
├── buttons/
│ ├── _global/
│ ├── crumbSaucer/
│ ├── crumbleStrike/
│ ├── poker/
│ └── profile/
├── commands/
│ ├── applications/
│ └── miscellaneous/
├── events/
├── handlers/
├── modals/
├── selectMenus/
│ └── poker/
├── utils/
├── index.js
├── README.md
├── package.json
└── .env # never commit
Important rule:
Discord wrapper
↓
shared utility
↓
Mongo/economy/profile systems
If behavior can be reached from multiple interfaces, the implementation should exist once in a utility rather than being copied into commands/buttons.
Example:
/cookies collect
│
▼
crumbCollect.js
▲
│
Crumb Saucer Bakery
3. Runtime / Boot Architecture
index.js is the runtime entry point.
It:
- Loads environment variables.
- Creates and exports the Mongo client.
- Creates and exports the Discord client.
- Initializes Discord.js collections for:
- commands
- legacy commands
- aliases
- buttons
- select menus
- modals
- Connects MongoDB before exposing handlers.
- Initializes important indexes/systems, including Cookie and profile indexes.
- Loads handler modules from
handlers/. - Logs the Discord client in.
Relevant environment variables include:
D_TOKEN
M_URI
M_DB
BOT_AUTHOR_ID
PROFILE_TIMEZONE
Current profile timezone fallback is:
Europe/Berlin
Do not import/use an uninitialized Mongo connection during module bootstrap if it creates a circular-initialization problem. Several utilities intentionally require mClient from index.js only after index.js has exported it.
4. Handler / Discovery Model
ShaiBot uses filesystem discovery.
Handlers populate Discord.js collections such as:
client.commands
client.legacyCommands
client.aliases
client.buttons
client.selectMenus
client.modals
Do not manually wire every new button into InteractionCreate.js if the existing loader can discover it.
Before adding a new interaction type:
- Inspect the relevant handler loader.
- Match the expected export shape.
- Match the existing
customIdnaming convention. - Keep the module thin.
events/InteractionCreate.js is the central router for interactions. Poker also required String Select Menu routing, so do not assume only buttons/slash commands are handled.
5. Discord Interaction Conventions
General project behavior:
- User-facing game/menu output is usually public.
- Unauthorized/error/cooldown/debug-style feedback may be ephemeral.
- Existing messages are edited where practical.
- Animation-style systems should be rate-limit conscious.
- Button visibility is never authorization.
- Critical state must not live only in an embed/footer.
Potentially slow interactions
For new code that may perform:
- MongoDB work
- economy changes
- persistent game transitions
- profile evaluation
- public-message refreshes
prefer acknowledging early.
For commands:
await interaction.deferReply({
flags: MessageFlags.Ephemeral
});
For component flows that intentionally edit an existing message, use the interaction response method appropriate for that handler.
Poker specifically uses early ephemeral deferral for stateful actions.
Do not globally defer every interaction from the dispatcher. Different handlers legitimately use reply, update, deferReply, or deferUpdate.
6. Interaction Ownership / Sessions
utils/interactionSession.js centralizes single-owner interaction sessions.
Its job is to make a public interactive message visible to everyone while restricting controls to the session owner.
The session layer validates:
session.ownerId === interaction.user.id
and can validate session kind.
It also supports legacy owner inference for older footer-based sessions.
Use this utility for single-owner interaction sessions where it fits.
Do not force true multiplayer systems such as Poker into an owner-only session abstraction. Multiplayer authorization must come from the multiplayer game state.
When closing a managed interaction session, mark it inactive using the shared session utility instead of inventing a second ownership record.
7. Permissions
utils/permissions.js centralizes admin/author checks.
Important concept:
Allowed administrative user
=
Discord Administrator
OR
BOT_AUTHOR_ID from .env
Use requireAdminAccess() for admin maintenance flows rather than reimplementing permission checks.
BOT_AUTHOR_ID is optional and comes from:
process.env.BOT_AUTHOR_ID
Never hard-code an author Discord ID in application logic.
8. MongoDB Philosophy
ShaiBot intentionally supports MongoDB deployments that do not rely on multi-document transactions.
Do not casually introduce transaction requirements.
The project instead uses combinations of:
- atomic filtered updates
- unique indexes
- compare-and-swap/version checks
- compensating writes
- idempotency markers
- persistent recovery state
When modifying money or persistent game state, reason about process crashes between every important write.
9. Important MongoDB Collections
Current important collections include:
items_cookies
cooldown_cookies
items_games
items_special_games
stats_profiles
items_achievements
guild_badges
config_achievements
Other feature-specific collections may exist. Read the utility owning that system rather than assuming collection names.
items_cookies
Cookie balances.
Core shape:
{
userId: "...",
cookies: 2500
}
Some systems may temporarily add fields such as:
appliedCookieOperations
stealable
These fields have lifecycle semantics; do not remove them blindly.
cooldown_cookies
Per-user/per-guild cooldown document.
Conceptual shape:
{
userId,
guildId,
collect: <unix timestamp>,
steal: <unix timestamp>,
heist: <unix timestamp>
}
Cooldown values are Unix timestamps indicating availability.
items_games
Existing persistent game/session state used by legacy/simple systems.
Do not migrate a stable game out of this collection merely for aesthetic consistency unless there is a real architectural need.
items_special_games
Persistent state for lifecycle-heavy/special games.
Currently used by Poker with a common envelope and game-specific payload.
Keep shared envelope concerns generic but do not build a giant generic game engine.
Profile collections
utils/profileSystem.js owns:
stats_profiles
items_achievements
guild_badges
config_achievements
Treat these collection names as part of the profile system contract. Do not duplicate profile collection logic throughout command/button modules.
10. Cookie Economy — Source of Truth
utils/cookieEconomy.js is the shared economy utility.
Use it rather than writing ad-hoc balance updates in game/application files.
Important primitives include concepts such as:
getCookieBalance
changeCookies
transferCookies
getCooldown
setCooldown
claimCooldown
Cookie vault helpers
idempotent Cookie operations for Poker
temporary ephemeral reply helpers
index initialization
Balance safety
Negative balance changes use an atomic filter:
cookies >= amount
If the debit cannot be applied, the utility throws:
INSUFFICIENT_COOKIES
Code may represent expected failures via error.message rather than error.code.
Transfers
Transfers intentionally do not depend on Mongo transactions.
Safe conceptual sequence:
atomic debit source
→ credit destination
→ if destination credit fails, best-effort compensate source
Critical compensation failure must remain visible in logs.
Never implement a second transfer function inside a command.
Cooldowns
Use claimCooldown() for actions where simultaneous/double-click interactions must not both claim the same cooldown.
It handles insert/update races with unique indexes.
11. Cookie Operation Idempotency
Some systems, especially Poker, use idempotent Cookie operation IDs.
Temporary field:
appliedCookieOperations: [
"poker_buyin:...",
"poker_cashout:..."
]
Purpose:
operation retries after crash
must not debit/pay twice
Lifecycle rule:
- Marker exists during uncertain/recoverable period.
- Persistent game state confirms operation.
- Marker is cleared after durable success.
- If the array becomes empty, unset the field rather than retaining
[].
Never clear an idempotency marker before recovery is safe without it.
12. /cookies Application
commands/applications/cookies.js provides the main Cookie user interface.
Current subcommands include:
collect
steal
check
give
leaderboard
Collect
/cookies collect delegates to utils/crumbCollect.js.
The Crumb Saucer Bakery must use the same shared collection implementation so both interfaces share:
- cooldown
- reward generation
- balance update
- profile tracking
Do not reintroduce a separate Bakery collection algorithm.
Give
Use the shared economy transfer path.
Profile metadata currently distinguishes giving with values such as:
type: "cookies_given"
source: "give"
targetUserId
and recipient gain may carry:
type: "cookie_gain"
source: "give"
senderUserId
Preserve this metadata because achievements/social relationship tracking depend on it.
13. PvP Cookie Stealing
Normal player-to-player stealing is separate from Vault Heist.
The current steal model is documented in docs/ECONOMY.md.
Key current balance values:
Cooldown: 15 minutes
Base/equal-wealth chance: 55%
Success range: 30%–80%
Wealth scaling: 12.5 points per 10x wealth ratio
Heat per recent attempt: 7.5 points
Heat lifetime: 1 hour, linear decay
Victim hourly loss budget: 15% of balance at window start
Per-hit hard cap: 25,000 Cookies
Loot bands:
50% → 1–3%
30% → 3–5%
15% → 5–8%
5% → 8–10%
Final loot is modified by difficulty and capped by:
- victim balance
- remaining hourly exposed budget
- hard cap
Victim heat
Profile-side stealProtection.attempts tracks recent valid robbery attempts.
All attackers contribute to the same victim heat.
The current attempt must not penalize itself.
Victim exposure budget
items_cookies.stealable may contain:
{
amount,
windowStartedAt
}
The first valid attempt after the window expires opens/breaches the vault and initializes exposure.
Successful robbery reserves exposure atomically before transferring Cookies.
If the transfer fails, refund the reservation.
Do not consume the attacker's cooldown for a robbery rejected only because the victim's exposure budget is exhausted.
14. Crumb Saucer Hub
utils/crumbSaucer.js is the shared hub renderer/logic.
commands/applications/crumbSaucer.js opens the hub.
Buttons live primarily under:
buttons/crumbSaucer/
Current hub-connected features include:
- Cookie Bakery
- Slots
- Blackjack
- Roulette
- Vault Heist
- Bankruptcy
- Profile
- Poker
Do not add a new top-level command for a feature whose product requirement is to live inside the Saucer.
15. Cookie Bakery / crumbCollect.js
utils/crumbCollect.js is authoritative for Cookie collection.
The Bakery and /cookies collect share it.
Current conceptual behavior:
claim shared "collect" cooldown
→ generate reward
→ change Cookies
→ track profile Cookie gain
→ return amount / next timestamp / balance
Keep the same cooldown key across all collection interfaces.
16. Slots
Core logic:
utils/crumbSlots.js
UI buttons:
buttons/crumbSaucer/slots*.js
Features include:
- wager buttons
- symbol reels
- triplet payouts
- jackpot
- Run It Back
- profile events
- first-time global triplet badges
Historical caution:
Slots does not currently require a dedicated All-In button to derive All-In state. A wager equal to the player's committed available balance can still be considered All-In where the metadata logic requires it.
Run It Back metadata interfaces include compatibility fields such as:
previousAllIn
previousBet / previousWager compatibility
Do not remove compatibility reads casually; old/persisted sessions may still rely on them.
17. Blackjack
Core logic:
utils/crumbBlackjack.js
Important behavior:
- Hit
- Stand
- Double
- Dealer stands on 17
- Natural Blackjack pays 3:2
- Push returns wager
- All-In
- Run It Back
- persistent/owned active hands
- replay metadata
- profile/achievement/badge integration
Immutable wager history
All-In and Run It Back achievements must use immutable wager-time metadata.
Do not infer a historical All-In from the player's current Cookie balance.
This exists specifically to handle cases such as:
player goes All-In
→ hand pushes
→ Cookies are gifted to player
→ player uses Run It Back for the original amount
The sequence must remain valid even though the live balance changed.
Resolution safety
Protect hand resolution against rapid repeated interactions.
A result must not pay twice.
18. Roulette
Core logic:
utils/crumbRoulette.js
European roulette uses:
0–36
Current simple bets include:
- Red
- Black
- Odd
- Even
- Zero
Zero loses for Red/Black/Odd/Even.
Roulette uses rate-limit-conscious message edits for pseudo-animation.
It supports All-In and Run It Back metadata/profile tracking.
Do not convert the animation into a high-frequency message spam loop.
19. Vault Heist
Core logic:
utils/cookieHeist.js
The Saucer exposes approaches such as:
Sneak
Hack
Smash
All approaches share the same Heist cooldown.
Heist has risk/reward rules with caps and profile integration.
Heist is a game against the bot/house vault and is separate from PvP /cookies steal.
Profile tracking includes both:
- generic game outcome
- vault-specific stolen amount
Do not collapse those into one event if both semantics are used by achievements/stats.
20. Bankruptcy
Core logic:
utils/bankruptcy.js
Bankruptcy is a starter/recovery mechanic.
Profile integration tracks:
special.bankruptcyCount
special.bankruptcyRecoveryBest
Recovery progression currently supports achievements around rebuilding after Bankruptcy.
Important invariant:
bankruptcyRecoveryBest should reflect actual vault balance after Bankruptcy, not merely lifetime Cookie gains.
A new Bankruptcy resets the current recovery run while previously unlocked achievements remain unlocked.
21. Run It Back
Run It Back exists across supported Saucer games.
Profile system tracks per-game consecutive Run It Back use.
Concept:
source = "run_it_back"
A normal/new wager breaks that game's chain.
Outcomes such as win/loss/push do not inherently break the streak because the streak represents the player's decision to immediately replay.
Current supported games include:
slots
blackjack
roulette
Use canonical wager metadata for new events.
Compatibility reads for old bet/previousBet may intentionally remain in profileSystem.js.
22. "I Can Do This All Day"
Cross-game sequence:
All-In
→ round resolves
→ Run It Back
→ same committed wager
The previous All-In and previous wager are stored as immutable historical metadata.
Do not replace this with a check against current vault amount.
Global first-time badge and normal achievement may both exist for this concept.
23. Profile System — Central Contract
utils/profileSystem.js is the central profile/progression module.
It owns:
stats_profiles
items_achievements
guild_badges
config_achievements
Major responsibilities include:
- default stats schema
- profile stats reads/writes
- profile event tracking
- game outcomes
- Run It Back tracking
- activity/day/time tracking
- social relationship tracking
- achievement requirement evaluation
- event-condition achievements
- achievement notifications
- global badge claiming
- profile rendering
- category formatting
- profile indexes/system initialization
Do not build game-specific alternative achievement storage.
24. Profile Event Vocabulary
Prefer adding metadata to established events rather than inventing redundant parallel event systems.
Common examples:
{
type: "game_start",
game: "blackjack",
source: "normal" | "run_it_back",
wager,
allIn,
previousAllIn,
previousBet
}
{
type: "game_result",
game,
result: "win" | "loss" | "push",
profit
}
{
type: "cookie_gain",
source: "collect" | "give" | "steal" | "bankruptcy",
amount
}
{
type: "cookies_given",
source: "give",
amount,
targetUserId
}
{
type: "steal_success",
source: "steal",
amount,
targetUserId
}
{
type: "stolen_from_me",
source: "steal",
amount,
thiefUserId
}
Metadata conventions:
field/property names → camelCase
enum/string values → snake_case where applicable
achievement/badge IDs → snake_case
For newer wager event metadata:
wager
is preferred over:
bet
Compatibility fallbacks may remain intentionally.
25. Activity / Time Tracking
Profiles track activity using a configured timezone.
Current default:
Europe/Berlin
Activity includes concepts such as:
currentDailyStreak
bestDailyStreak
lastActiveDate
lastGameDate
currentWeekKey
currentWeekDays
gamesByWeekday
gamesByTimeBucket
Time buckets:
overnight
morning
afternoon
evening
Achievement design preference:
- normal progression should use broad buckets
- exact late-night hours should not be required for meaningful completion
- optional hidden overnight content is acceptable
Do not scatter hour-boundary definitions through games; use centralized activity context.
26. Daily Narrative State
Profiles also maintain bounded/current-day state for narrative/composite achievements.
Conceptual fields include:
daily.dateKey
daily.gamesPlayed
daily.gamesWon
daily.gamesLost
daily.collectedCookies
daily.gambledAfterCollect
daily.wonAnyGame
daily.lostAnyGame
daily.gaveCookies
daily.stoleCookies
Daily state resets when the profile activity date changes.
Do not turn this into an unbounded event history.
27. Social Relationship State
Profiles track durable relationship sets such as:
social: {
giftedTo: [],
receivedFrom: [],
successfullyStolenFrom: [],
successfullyStolenBy: []
}
Use $addToSet semantics because these arrays represent unique users.
Current social achievement scale is intentionally suited to a small community:
5 unique players
10 unique players
Avoid introducing 25/50-user requirements unless the community size changes materially.
Derived relationship flags include concepts such as:
Et Tu, Crumb?
No Hard Feelings
Cookie Laundering
Historical gifting relationships cannot be fabricated if recipient IDs were not stored historically.
28. Achievement Model
Achievements are personal progression and can be earned by multiple players.
Definitions live in:
config_achievements
Unlocks live in:
items_achievements
Stat-backed achievements
Use a stat path and target.
These can usually be reconciled/backfilled.
Composite achievements
Measures such as:
all
any
evaluate condition arrays.
Supported condition concepts include comparison operators and array membership.
Event achievements
Use:
measure: "event"
eventType
eventConditions
eventMode
They depend on the current event context and generally must not be historically invented.
Event operators include concepts such as:
eq
neq
gt
gte
lt
lte
in
not_in
truthy
falsy
Collector achievements
achievementPercentage achievements should use the same definition of "collectible" everywhere.
Event-only achievements are intentionally excluded from Completionist-style denominators where they cannot reliably be backfilled.
Do not let the profile UI denominator disagree with the evaluator denominator.
29. Achievement Notifications
Achievement unlock notification is centralized.
Do not independently announce achievements from each game.
The notification should mention the user who earned the achievement where supported by the current implementation.
If an achievement is inserted directly by maintenance reconciliation, public live-game notification is intentionally not required.
Maintenance writes should log what was backfilled.
30. Global / Guild-Unique Badges
Badges are unique guild-wide collectibles.
Collection:
guild_badges
Typical atomic claim pattern:
guildId
badgeId
ownerId: null
then atomically set:
ownerId
unlockedAt
metadata
This guarantees simultaneous candidates cannot both own the same badge.
Never implement a guild-first badge as:
find unclaimed
→ then separate uncontrolled update
Manual/hidden badges may be intentionally assigned directly in MongoDB.
Current examples include:
- game firsts
- Slots triplets
- All-In/Run It Back firsts
- hidden bug-hunter/beta-tester badges
- Poker firsts
Keep historical badge IDs stable once they may have owners.
31. Profile Maintenance / Admin
utils/profileMaintenance.js provides reconciliation/audit behavior.
commands/applications/profileAdmin.js exposes administrator tools.
Admin access uses the shared permission utility.
Current concepts include:
/profileadmin reconcile
/profileadmin audit
maintenance reward/status
Reconciliation
Reconciliation:
- evaluates stat-backed/backfillable achievements
- skips event-only achievements
- can dry-run
- logs actual/dry-run unlocks
Do not treat a high event-only skip count as an error by itself.
Audit
Audit validates structural consistency such as:
- game W/L/P totals
- non-negative economy counters
- activity schema
- date/week keys
- achievement definition validity
- composite operators
- event operators
- orphaned achievement docs
- badge counts
- maintenance reward claim arrays
- Poker profile fields where integrated
Maintenance rewards
Maintenance compensation is designed to be idempotent by reward ID.
It should not inflate lifetime gameplay/economy achievements simply because the bot was offline.
32. Achievement / Badge ID Stability
Once users may have unlocked an ID, treat it like a database API key.
Prefer:
active: false
legacy: true
over deleting or renaming a historical definition.
When replacing tiers, preserve old unlock data and introduce the new live IDs/tier definitions deliberately.
33. Profile Categories
Category labels are formatted centrally.
Common categories include:
economy
stealing
victim
vault
slots
blackjack
roulette
heist
poker
collector
activity
other
games
If a new category appears as a generic 🏅 Name, add an explicit category label only when that category is truly intended.
Do not create an empty UI category solely for naming aesthetics.
34. Poker — Architecture
Poker is a special persistent multiplayer game inside the Crumb Saucer.
Important files:
utils/pokerEngine.js
utils/crumbPoker.js
utils/pokerDiscord.js
utils/pokerInteractions.js
utils/pokerProfile.js
handlers/poker.js
buttons/poker/*
selectMenus/poker/*
See any Poker-specific guide in the repository for deeper details.
Core product rules:
2–6 human players
Crumb Saucer entry only
no /poker command
1,000 default buy-in
10 / 20 blinds
90 second turns
single pot only
no side pots
no tournaments
no bots
no rake
no spectators in V1
Poker persistence
items_special_games is temporary authoritative live/recovery state.
Game records and membership records coexist there using gameType/recordType.
Settled games are deleted after all value is safely returned.
Permanent history goes to profiles—not raw closed game documents.
Poker concurrency
Authoritative game records use optimistic version updates.
Never perform broad unversioned authoritative writes from interaction handlers.
Mongo _id must be stripped from domain objects before replacement. A previous bug demonstrated that cloning BSON ObjectId and feeding it into replaceOne() can trigger immutable _id errors.
Poker economy
Cookie economy crosses the Poker boundary only for:
buy-in
cash-out/refund
Betting uses internal stacks.
Idempotent operation markers protect recovery.
Poker privacy
Never expose opponent hole cards publicly or in another player's private response.
A live player equity calculator was intentionally deferred for fairness.
Poker UI
Public embed emphasizes:
- cards dealt
- street
- pot
- community cards
- positions
- current turn
- remaining turn time
- result/hand complete state
Private Cards view reminds the player whether it is their turn.
Poker lifecycle
Table concepts include:
WAITING
PREFLOP
FLOP
TURN
RIVER
NEXT_HAND
CLOSING
CLOSED (short transition before cleanup)
NEXT_HAND is rendered as HAND COMPLETE for users.
Lobby timeout is disabled during active hands and refreshed after a hand ends.
Timeout action uses the same authoritative Fold transition.
Host cancellation outside active hands cashes out everyone safely and then cleans the game record.
35. Poker Rules / Single Pot
ShaiBot Poker is simplified Texas Hold'em.
Hole cards:
2 per player
Community:
Flop 3
Turn 1
River 1
Actions:
Fold
Check
Call
Bet/Raise
All-In
Hand ranking:
Royal Flush
Straight Flush
Four of a Kind
Full House
Flush
Straight
Three of a Kind
Two Pair
Pair
High Card
Single-pot invariant
No side pots.
At hand start:
handCap = smallest participating starting stack
Each player can commit at most that cap for the hand.
Do not remove this cap unless side pots are intentionally designed across engine, persistence, UI, tests, and settlement.
Heads-up positions:
Dealer = Small Blind
other player = Big Blind
So UI [D/SB] is correct.
36. Poker Profile Integration
Permanent Poker history belongs in:
stats_profiles.games.poker
Concepts include:
- hands played/won/lost/pushed
- Cookies won/lost
- largest pot won
- All-Ins / All-In wins
- showdowns / showdown wins
- action counts
- fold wins
- full-table wins
- pocket Aces wins
- 7-2 wins
- win/loss streaks
- hand-rank counters
Poker profile writes happen after authoritative Poker state succeeds.
A profile failure must never corrupt/rollback a valid pot settlement.
Profile processing uses bounded de-duplication markers; do not turn them into permanent unbounded logs.
Poker achievements/global badges use the existing profile/badge system, not a second Poker-specific achievement database.
37. Crumble Strike
Core logic:
utils/crumbleStrike.js
Buttons:
buttons/crumbleStrike/
Handler/recovery:
handlers/crumbleStrike.js
Crumble Strike is a cooperative multiplayer boss battle with significantly more lifecycle complexity than ordinary Saucer games.
Core concepts include:
- encounter spawning
- recruitment
- author-controlled start
- later joining
- encounter lifetime
- committed Cookie stakes
- victory/failure cleanup
- DPS/Tank/Healer roles
- job selections
- tiers
- HP
- Energy
- Limit resource
- revive progress
- boss state
- persistent recovery
Actions include:
Strike
Defend
Heal
Revive
Limit Break
Revive behavior:
normal roles → +1 progress
Healers → +2 progress
3 progress → revive
Do not copy Crumble Strike complexity into simpler games merely because it is multiplayer.
When modifying it, trace encounter expiry/recovery and Cookie settlement carefully.
38. Raid Planner
Core utility:
utils/raidPlanner.js
Raid planning uses Discord embeds/reactions.
Participation may count unique participants across reactions without forcibly removing reactions.
Reaction add/remove events are routed through the event system.
Be mindful of Discord partials because reaction/message/user partials are enabled.
39. Records / Presentation Utilities
The repository includes:
utils/profileRecords.js
commands/applications/records.js
and profile display code.
Before adding a duplicate "records" or statistics rendering path, inspect these modules.
Keep presentation logic separate from authoritative mutation logic.
40. Error Handling Conventions
Expected domain errors often use stable strings:
INSUFFICIENT_COOKIES
SAME_ACCOUNT
INVALID_COOKIE_AMOUNT
OUT_OF_TURN
STATE_CONFLICT
...
Some code stores the identifier in:
error.message
other code may use:
error.code
When mapping expected errors to friendly UI, support the conventions used by that subsystem.
Do not expose raw internal identifiers when a user-friendly message exists.
Unexpected errors should be logged/propagated rather than silently converted into misleading user errors.
41. Concurrency Rules
Assume users will:
- double-click
- spam buttons
- act simultaneously
- race timeouts
- change balances between screens
- restart the bot mid-operation
For any new stateful/economy feature, ask:
Can two interactions both read the same old state?
Can both pay?
Can both debit?
Can timeout and click both win?
Can a process restart after money changes but before state persists?
Use the existing pattern appropriate to the subsystem:
- atomic filtered Mongo update
- unique index
- cooldown claim
- state/version CAS
- idempotent operation ID
- result/resolution marker
- compensating write
Do not rely on JavaScript process-local flags for correctness that must survive restart.
42. Embed / Footer State
Historical ShaiBot flows sometimes encode owner/session information in embed footers.
Modern rule:
- footer state may be used for navigation/legacy compatibility
- do not treat embed/footer content as authoritative economy/game state
- prefer persistent DB/session records for critical state
When editing old footer-based flows, preserve compatibility unless deliberately migrating all buttons that depend on it.
43. Public vs Private UI
Public by default:
- normal game output
- menus
- profile pages
- achievement announcements
- global badge announcements
Ephemeral/private where appropriate:
- unauthorized control feedback
- validation errors
- cooldown/debug notices
- Poker hole cards
- admin maintenance replies
- destructive confirmation dialogs
Do not accidentally make a public followUp() where the content contains private game information.
44. PM2 / Deployment
Typical production lifecycle:
node --check <modified-file.js>
pm2 restart ShaiBot
pm2 logs ShaiBot --lines 100
For multiple changed JS files, syntax-check each important entry/utility before restart.
A silent node --check is success.
Do not claim a deploy is safe solely because syntax checking passed; state/economy changes require live or controlled testing.
45. Testing Philosophy
The repository historically had limited automated testing, while Poker introduced pure-engine/state tests during development.
General recommendation:
- pure deterministic engines should have automated tests
- economy/state integration requires concurrency/recovery testing
- existing stable systems without tests should not be broadly rewritten merely to make them test-shaped
Useful manual race tests:
double click
two users act simultaneously
balance changes between screens
timeout vs click
restart during state transition
retry after partial write
When tests exist in the repository, preserve them when modifying the covered module.
46. Database Migrations
Prefer idempotent migrations.
Good migration behavior:
$ifNullfor new profile fields$mergeObjectswhen extending nested schemas- upsert definitions by stable ID
- preserve existing values
- preserve historical unlock IDs
- dry-run reconciliation before bulk unlock writes
Avoid:
- overwriting whole profile subdocuments unnecessarily
- blind destructive renames
- dropping historical achievement IDs
- inventing historical data that was never tracked
If data cannot be reconstructed reliably, start tracking from rollout date.
47. Indexes
Indexes are part of correctness, not only performance.
Examples:
items_cookies unique userId
cooldown_cookies unique userId + guildId
profile collections indexes established by profile initializer
items_special_games.gameId unique
Poker also uses indexes for:
- game type/guild/state
- player membership lookup
- Discord message lookup
- stale/updated game lookup
Before changing a unique-key assumption, inspect the initializer and production migration commands.
48. Schema Versioning / Auditing
Profiles currently use:
profileSchemaVersion >= 2
where expected by the current audit.
Do not bump a schema version casually without:
- default schema update
- migration
- audit update
- compatibility consideration
The audit is intended to reveal structural drift after rollouts.
49. Naming Conventions
General preferred naming:
JavaScript fields: camelCase
event values: snake_case where appropriate
achievement IDs: snake_case
badge IDs: snake_case
Mongo collections: existing repository convention
Do not rename IDs that have already escaped into Mongo simply for cosmetic consistency.
New event wager metadata should use:
wager
while existing compatibility fields may continue using:
previousBet
until deliberately migrated.
50. Source-of-Truth Hierarchy
For any subsystem, prefer this hierarchy:
authoritative DB / engine state
↓
shared utility
↓
Discord rendering
↓
footer/customId hints
Never reverse this by reconstructing critical facts from an embed when durable state exists.
51. Adding a New Crumb Saucer Game
Preferred process:
- Put game rules/shared state in
utils/. - Reuse
cookieEconomy.js. - Add thin buttons under the established directory.
- Reuse profile events/stats.
- Define authorization explicitly.
- Persist immutable wager metadata needed later.
- Protect resolution against repeated interactions.
- Check house liquidity for house-funded games.
- Use internal stacks instead of repeated balance mutation for multiplayer games where appropriate.
- Keep DB state authoritative.
- Add recovery if value can be stranded across restarts.
- Add audit/progression changes only after core economy correctness.
Do not create a top-level command when the product requirement is Saucer-only.
52. Adding an Achievement
Before adding an achievement, determine which type it is.
Lifetime/stat-backed
Use a durable stat.
Benefits:
- easy audit
- reconciliation
- progress display
Composite
Use existing durable state paths and all/any.
Event-only
Use when the accomplishment genuinely depends on the exact event context and cannot be reconstructed later.
Keep event-only accomplishments out of collector denominators where necessary.
Hidden
Set secret/hidden semantics in the definition rather than hiding it with ad-hoc UI code.
After inserting definitions:
/profileadmin audit
/profileadmin reconcile dry-run:true
53. Adding a Global Badge
Use the existing badge collection and atomic claim helper.
Decide:
- guild-unique?
- hidden?
- gameplay-claimed or manually awarded?
- metadata needed?
- meaningful "first" rather than arbitrary simultaneous completion?
Do not create "first" badges where multiple players necessarily complete the same event simultaneously unless tie ownership has a defined rule.
54. Maintenance Reward Design
Maintenance compensation is not ordinary gameplay income.
Keep it idempotent using a stable reward ID.
Do not route it through normal profile gain tracking if doing so would artificially unlock economy achievements.
Admin maintenance actions should:
- defer ephemerally
- support dry-run where destructive/bulk
- log results
- be safe to retry
55. Security / Secrets
Never commit:
.env
Discord bot token
Mongo credentials
private administrative secrets
If credentials are exposed, rotate them.
Do not print secrets into:
- public embeds
- user-facing errors
- repository documentation
- screenshots/logs intended for public use
56. Performance Guidance
Avoid unnecessary sequential Mongo round-trips.
However, do not sacrifice correctness for fewer calls.
Common performance priorities:
- acknowledge Discord interactions early when work may be slow
- reuse already-fetched documents/definitions where clean
- avoid querying achievement definitions multiple times in one pipeline when the same snapshot can be reused
- avoid high-frequency message edits
- keep arrays bounded when they are only for idempotency/recent history
Do not perform a broad performance refactor during an unrelated bug fix unless the issue is causing real failures.
57. Known Historical Failure Modes / Lessons
These patterns have caused bugs during development and are worth guarding against.
Missing/renamed exports
Buttons/admin commands import central utilities. If a helper is renamed or omitted from module.exports, runtime errors look like:
X is not a function
When changing shared utility exports, grep consumers first.
Embed footer assumptions
Some older profile/navigation buttons once assumed an embed/footer always existed. Use optional access and/or centralized session helpers.
Mongo immutable _id
Do not clone BSON _id into replacement domain objects.
Internal errors leaking to users
Expected internal errors such as INSUFFICIENT_COOKIES must be mapped to friendly text.
Stale interaction confirmation controls
After a destructive confirmation succeeds or is explicitly cancelled, remove/disable the confirmation controls.
Discord timeout
If state/profile work becomes slow, the interaction may succeed server-side but Discord displays "This interaction failed." New expensive interactions should acknowledge early.
Historical state inferred from current balance
Never determine old All-In/replay facts from current Cookies.
58. AI Agent Safe-Change Workflow
Before modifying code:
- Read this file.
- Read
README.md. - If touching Cookie balances, transfers, stealing, cooldowns, settlement, or economy idempotency, read docs/ECONOMY.md.
- Read every utility directly involved.
- Read its command/button consumers.
- Search for imports/exports of functions being changed.
- Identify the authoritative state location.
- Identify concurrency/restart requirements.
- Identify profile/achievement side effects.
- Make the smallest coherent change.
After modifying:
- Syntax-check changed JS.
- Search for stale imports/field names.
- Review Mongo update filters for race safety.
- Verify expected error mapping.
- Restart in a controlled environment.
- Inspect PM2 logs.
- Exercise the affected interaction.
- Inspect relevant Mongo document(s).
- Run profile audit/reconcile dry-run if profile schema/definitions changed.
- Update this guide if an architectural contract changed.
59. What Not to Do
Avoid these unless explicitly requested as a coordinated redesign:
- Mongo transaction dependency
- giant new framework around stable utilities
- duplicate Cookie balance logic
- duplicate profile system
- duplicate achievement/badge storage
- raw balance writes from game buttons
- trusting Discord button visibility for authorization
- storing critical state only in embeds
- global deferral of every interaction
- side-pot infrastructure for Poker
- deleting historical achievement/badge IDs
- unbounded idempotency/history arrays
- broad refactor while fixing one isolated bug
- inventing historical profile data that was never recorded
60. Repository Documentation Map
Use these docs together:
README.md
Human-facing project overview, features, setup, and documentation index.
AGENTS.md
Repository-wide architecture, invariants, and safety rules for AI agents.
docs/ARCHITECTURE.md
Runtime, handlers, persistence, concurrency, and recovery.
docs/ECONOMY.md
Complete Cookie economy manual, including PvP stealing mechanics,
formulas, heat, exposure budgets, transfers, settlement, and idempotency.
docs/GAMES.md
Crumb Saucer, Poker, Crumble Strike, and game mechanics.
docs/PROGRESSION.md
Profiles, achievements, badges, activity, social progression,
reconciliation, and maintenance.
If an older phase note disagrees with current implementation, do not regress current code to match the note.
61. Current Stable Design Principles
ShaiBot generally values:
shared utilities
thin Discord wrappers
atomic economy operations
explicit metadata
persistent critical state
recoverable multiplayer state
idempotent bulk/admin operations
centralized profile progression
public fun / private sensitive data
simple rules over unnecessary abstraction
When choosing between a clever abstraction and a small explicit implementation, prefer the latter unless reuse is already proven.
62. Final Invariants Checklist
Before shipping any stateful feature change, answer these.
Economy
Can Cookies duplicate?
Can Cookies disappear?
Can a debit/pay happen twice?
Is insufficient balance checked atomically?
If a multi-write operation fails halfway, is it recoverable/compensated?
Game state
What is authoritative?
Can two clicks both win?
What happens after restart?
What happens to stale buttons?
Can someone act on another user's session/turn?
Profile
Is the stat durable?
Should it be backfillable?
Does it affect collector percentage?
Can the same event be counted twice?
Does profile failure compromise game settlement?
Discord
Will the interaction be acknowledged in time?
Is private information public?
Does the UI reflect authoritative state?
Are destructive confirmation controls invalidated afterward?
Mongo
Are update filters race-safe?
Are unique indexes relied upon?
Are new fields migrated safely?
Are arrays bounded where appropriate?
Are historical IDs preserved?
If any answer is unclear, inspect the owning utility before shipping.
63. Maintenance of This File
Update AGENTS.md when any of these materially change:
- collection names
- runtime/handler loading
- economy sequencing
- core cooldown/steal rules
- profile schema/version
- achievement evaluation model
- badge claim model
- Crumb Saucer game list
- Poker state/rules/lifecycle
- Crumble Strike persistence/lifecycle
- authorization/session model
- admin/audit workflow
Do not update this file with every cosmetic UI tweak.
The purpose is to preserve architectural knowledge and safety invariants so future maintainers and AI agents can make changes confidently without re-learning the same failure modes.