2217 lines
44 KiB
Markdown
2217 lines
44 KiB
Markdown
# 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
|
||
- `/cookies` collection, 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:
|
||
|
||
```text
|
||
ShaiBot/
|
||
├── assets/
|
||
├── buttons/
|
||
│ ├── _global/
|
||
│ ├── crumbSaucer/
|
||
│ ├── crumbleStrike/
|
||
│ ├── poker/
|
||
│ └── profile/
|
||
├── commands/
|
||
│ ├── applications/
|
||
│ └── miscellaneous/
|
||
├── events/
|
||
├── handlers/
|
||
├── modals/
|
||
├── selectMenus/
|
||
│ └── poker/
|
||
├── utils/
|
||
├── index.js
|
||
├── COOKIE_LOGIC.md
|
||
├── README.md
|
||
├── package.json
|
||
└── .env # never commit
|
||
```
|
||
|
||
Important rule:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
/cookies collect
|
||
│
|
||
▼
|
||
crumbCollect.js
|
||
▲
|
||
│
|
||
Crumb Saucer Bakery
|
||
```
|
||
|
||
---
|
||
|
||
# 3. Runtime / Boot Architecture
|
||
|
||
`index.js` is the runtime entry point.
|
||
|
||
It:
|
||
|
||
1. Loads environment variables.
|
||
2. Creates and exports the Mongo client.
|
||
3. Creates and exports the Discord client.
|
||
4. Initializes Discord.js collections for:
|
||
- commands
|
||
- legacy commands
|
||
- aliases
|
||
- buttons
|
||
- select menus
|
||
- modals
|
||
5. Connects MongoDB before exposing handlers.
|
||
6. Initializes important indexes/systems, including Cookie and profile indexes.
|
||
7. Loads handler modules from `handlers/`.
|
||
8. Logs the Discord client in.
|
||
|
||
Relevant environment variables include:
|
||
|
||
```text
|
||
D_TOKEN
|
||
M_URI
|
||
M_DB
|
||
BOT_AUTHOR_ID
|
||
PROFILE_TIMEZONE
|
||
```
|
||
|
||
Current profile timezone fallback is:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
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:
|
||
|
||
1. Inspect the relevant handler loader.
|
||
2. Match the expected export shape.
|
||
3. Match the existing `customId` naming convention.
|
||
4. 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:
|
||
|
||
```js
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
{
|
||
userId: "...",
|
||
cookies: 2500
|
||
}
|
||
```
|
||
|
||
Some systems may temporarily add fields such as:
|
||
|
||
```js
|
||
appliedCookieOperations
|
||
stealable
|
||
```
|
||
|
||
These fields have lifecycle semantics; do not remove them blindly.
|
||
|
||
## `cooldown_cookies`
|
||
|
||
Per-user/per-guild cooldown document.
|
||
|
||
Conceptual shape:
|
||
|
||
```js
|
||
{
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
cookies >= amount
|
||
```
|
||
|
||
If the debit cannot be applied, the utility throws:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
appliedCookieOperations: [
|
||
"poker_buyin:...",
|
||
"poker_cashout:..."
|
||
]
|
||
```
|
||
|
||
Purpose:
|
||
|
||
```text
|
||
operation retries after crash
|
||
must not debit/pay twice
|
||
```
|
||
|
||
Lifecycle rule:
|
||
|
||
1. Marker exists during uncertain/recoverable period.
|
||
2. Persistent game state confirms operation.
|
||
3. Marker is cleared after durable success.
|
||
4. 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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
type: "cookies_given"
|
||
source: "give"
|
||
targetUserId
|
||
```
|
||
|
||
and recipient gain may carry:
|
||
|
||
```js
|
||
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 `COOKIE_LOGIC.md`.
|
||
|
||
Key current balance values:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
{
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
utils/crumbSlots.js
|
||
```
|
||
|
||
UI buttons:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
previousAllIn
|
||
previousBet / previousWager compatibility
|
||
```
|
||
|
||
Do not remove compatibility reads casually; old/persisted sessions may still rely on them.
|
||
|
||
---
|
||
|
||
# 17. Blackjack
|
||
|
||
Core logic:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
utils/crumbRoulette.js
|
||
```
|
||
|
||
European roulette uses:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
utils/cookieHeist.js
|
||
```
|
||
|
||
The Saucer exposes approaches such as:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
utils/bankruptcy.js
|
||
```
|
||
|
||
Bankruptcy is a starter/recovery mechanic.
|
||
|
||
Profile integration tracks:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
{
|
||
type: "game_start",
|
||
game: "blackjack",
|
||
source: "normal" | "run_it_back",
|
||
wager,
|
||
allIn,
|
||
previousAllIn,
|
||
previousBet
|
||
}
|
||
```
|
||
|
||
```js
|
||
{
|
||
type: "game_result",
|
||
game,
|
||
result: "win" | "loss" | "push",
|
||
profit
|
||
}
|
||
```
|
||
|
||
```js
|
||
{
|
||
type: "cookie_gain",
|
||
source: "collect" | "give" | "steal" | "bankruptcy",
|
||
amount
|
||
}
|
||
```
|
||
|
||
```js
|
||
{
|
||
type: "cookies_given",
|
||
source: "give",
|
||
amount,
|
||
targetUserId
|
||
}
|
||
```
|
||
|
||
```js
|
||
{
|
||
type: "steal_success",
|
||
source: "steal",
|
||
amount,
|
||
targetUserId
|
||
}
|
||
```
|
||
|
||
```js
|
||
{
|
||
type: "stolen_from_me",
|
||
source: "steal",
|
||
amount,
|
||
thiefUserId
|
||
}
|
||
```
|
||
|
||
Metadata conventions:
|
||
|
||
```text
|
||
field/property names → camelCase
|
||
enum/string values → snake_case where applicable
|
||
achievement/badge IDs → snake_case
|
||
```
|
||
|
||
For newer wager event metadata:
|
||
|
||
```text
|
||
wager
|
||
```
|
||
|
||
is preferred over:
|
||
|
||
```text
|
||
bet
|
||
```
|
||
|
||
Compatibility fallbacks may remain intentionally.
|
||
|
||
---
|
||
|
||
# 25. Activity / Time Tracking
|
||
|
||
Profiles track activity using a configured timezone.
|
||
|
||
Current default:
|
||
|
||
```text
|
||
Europe/Berlin
|
||
```
|
||
|
||
Activity includes concepts such as:
|
||
|
||
```text
|
||
currentDailyStreak
|
||
bestDailyStreak
|
||
lastActiveDate
|
||
lastGameDate
|
||
currentWeekKey
|
||
currentWeekDays
|
||
gamesByWeekday
|
||
gamesByTimeBucket
|
||
```
|
||
|
||
Time buckets:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```js
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
config_achievements
|
||
```
|
||
|
||
Unlocks live in:
|
||
|
||
```text
|
||
items_achievements
|
||
```
|
||
|
||
## Stat-backed achievements
|
||
|
||
Use a stat path and target.
|
||
|
||
These can usually be reconciled/backfilled.
|
||
|
||
## Composite achievements
|
||
|
||
Measures such as:
|
||
|
||
```text
|
||
all
|
||
any
|
||
```
|
||
|
||
evaluate condition arrays.
|
||
|
||
Supported condition concepts include comparison operators and array membership.
|
||
|
||
## Event achievements
|
||
|
||
Use:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
guild_badges
|
||
```
|
||
|
||
Typical atomic claim pattern:
|
||
|
||
```text
|
||
guildId
|
||
badgeId
|
||
ownerId: null
|
||
```
|
||
|
||
then atomically set:
|
||
|
||
```text
|
||
ownerId
|
||
unlockedAt
|
||
metadata
|
||
```
|
||
|
||
This guarantees simultaneous candidates cannot both own the same badge.
|
||
|
||
Never implement a guild-first badge as:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
/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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
2 per player
|
||
```
|
||
|
||
Community:
|
||
|
||
```text
|
||
Flop 3
|
||
Turn 1
|
||
River 1
|
||
```
|
||
|
||
Actions:
|
||
|
||
```text
|
||
Fold
|
||
Check
|
||
Call
|
||
Bet/Raise
|
||
All-In
|
||
```
|
||
|
||
Hand ranking:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
Dealer = Small Blind
|
||
other player = Big Blind
|
||
```
|
||
|
||
So UI `[D/SB]` is correct.
|
||
|
||
---
|
||
|
||
# 36. Poker Profile Integration
|
||
|
||
Permanent Poker history belongs in:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
utils/crumbleStrike.js
|
||
```
|
||
|
||
Buttons:
|
||
|
||
```text
|
||
buttons/crumbleStrike/
|
||
```
|
||
|
||
Handler/recovery:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
Strike
|
||
Defend
|
||
Heal
|
||
Revive
|
||
Limit Break
|
||
```
|
||
|
||
Revive behavior:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
INSUFFICIENT_COOKIES
|
||
SAME_ACCOUNT
|
||
INVALID_COOKIE_AMOUNT
|
||
OUT_OF_TURN
|
||
STATE_CONFLICT
|
||
...
|
||
```
|
||
|
||
Some code stores the identifier in:
|
||
|
||
```js
|
||
error.message
|
||
```
|
||
|
||
other code may use:
|
||
|
||
```js
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
- `$ifNull` for new profile fields
|
||
- `$mergeObjects` when 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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
profileSchemaVersion >= 2
|
||
```
|
||
|
||
where expected by the current audit.
|
||
|
||
Do not bump a schema version casually without:
|
||
|
||
1. default schema update
|
||
2. migration
|
||
3. audit update
|
||
4. compatibility consideration
|
||
|
||
The audit is intended to reveal structural drift after rollouts.
|
||
|
||
---
|
||
|
||
# 49. Naming Conventions
|
||
|
||
General preferred naming:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
wager
|
||
```
|
||
|
||
while existing compatibility fields may continue using:
|
||
|
||
```text
|
||
previousBet
|
||
```
|
||
|
||
until deliberately migrated.
|
||
|
||
---
|
||
|
||
# 50. Source-of-Truth Hierarchy
|
||
|
||
For any subsystem, prefer this hierarchy:
|
||
|
||
```text
|
||
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:
|
||
|
||
1. Put game rules/shared state in `utils/`.
|
||
2. Reuse `cookieEconomy.js`.
|
||
3. Add thin buttons under the established directory.
|
||
4. Reuse profile events/stats.
|
||
5. Define authorization explicitly.
|
||
6. Persist immutable wager metadata needed later.
|
||
7. Protect resolution against repeated interactions.
|
||
8. Check house liquidity for house-funded games.
|
||
9. Use internal stacks instead of repeated balance mutation for multiplayer games where appropriate.
|
||
10. Keep DB state authoritative.
|
||
11. Add recovery if value can be stranded across restarts.
|
||
12. 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:
|
||
|
||
```text
|
||
/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:
|
||
|
||
```text
|
||
.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:
|
||
|
||
```text
|
||
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:
|
||
|
||
1. Read this file.
|
||
2. Read `README.md`.
|
||
3. If touching Cookies/steal logic, read `COOKIE_LOGIC.md`.
|
||
4. Read every utility directly involved.
|
||
5. Read its command/button consumers.
|
||
6. Search for imports/exports of functions being changed.
|
||
7. Identify the authoritative state location.
|
||
8. Identify concurrency/restart requirements.
|
||
9. Identify profile/achievement side effects.
|
||
10. Make the smallest coherent change.
|
||
|
||
After modifying:
|
||
|
||
1. Syntax-check changed JS.
|
||
2. Search for stale imports/field names.
|
||
3. Review Mongo update filters for race safety.
|
||
4. Verify expected error mapping.
|
||
5. Restart in a controlled environment.
|
||
6. Inspect PM2 logs.
|
||
7. Exercise the affected interaction.
|
||
8. Inspect relevant Mongo document(s).
|
||
9. Run profile audit/reconcile dry-run if profile schema/definitions changed.
|
||
10. 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:
|
||
|
||
```text
|
||
README.md
|
||
Broad feature/product overview and contribution conventions.
|
||
|
||
COOKIE_LOGIC.md
|
||
Detailed /cookies steal/robbery rules and collection contracts.
|
||
|
||
AGENTS.md
|
||
Cross-repository AI-agent architectural/safety guide.
|
||
|
||
Poker-specific agent guide (if present)
|
||
Deep Poker state/economy/rules/recovery contract.
|
||
|
||
Phase/rollout notes (if retained)
|
||
Historical implementation context; current code still wins.
|
||
```
|
||
|
||
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:
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
Will the interaction be acknowledged in time?
|
||
Is private information public?
|
||
Does the UI reflect authoritative state?
|
||
Are destructive confirmation controls invalidated afterward?
|
||
```
|
||
|
||
## Mongo
|
||
|
||
```text
|
||
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.
|