Files
2026-09-13 22:10:34 +02:00

2221 lines
44 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
├── 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 `docs/ECONOMY.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 Cookie balances, transfers, stealing, cooldowns, settlement, or economy idempotency, read docs/ECONOMY.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
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:
```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.