# Leaderboard bot integration The Discord bot owns all writes to `user_levels` and `voice_time`. The website only subscribes to notifications and then reads the committed database state. ## Level formula contract The website's personal profile calculates next-level progress from the bot's stored level and cumulative XP. Both services must use this exact formula: ```text required(0) = 500 required(level) = required(level - 1) + 100 + (level * 25) total(0) = 0 total(level) = total(level - 1) + required(level - 1) ``` `required(level)` is the XP needed to advance from that level to the next. `total(level)` is the cumulative XP needed to reach that level. Level 200 is the maximum; the XP requirement caps at the level-199 value and the profile does not show next-level progress at the cap. Store cumulative XP as an integer value and update `xp` and `level` in the same bot transaction. Changing the formula or maximum level requires a coordinated website update because the website intentionally does not infer the formula from database metadata. ## Redis channel Publish leaderboard notifications to exactly: ```text erika:sse:leaderboards ``` The bot and website use the same Redis deployment. Do not add Redis credentials to source code or event payloads. ## Event envelopes XP changes use this exact JSON envelope: ```json {"event":"xp","data":{}} ``` Voice-time changes use the affected Discord channel ID: ```json {"event":"vc","data":{"channelId":"123456789012345678"}} ``` No additional envelope or data fields are accepted. `channelId` is a Discord snowflake represented as a decimal string. ## Publish ordering 1. Start and complete the bot’s database transaction. 2. Confirm that the transaction committed successfully. 3. Publish the corresponding Redis event or events. Never publish before commit, from a rollback path, or from a `finally` block. The notification tells an open website page to read the database again, so publishing before commit can produce a stale refresh. ## Event cardinality - Publish one XP event when one transaction changes XP data. - Publish one VC event per affected channel when one transaction changes voice time. - When one transaction changes both XP and voice time, publish one XP event and one VC event for each affected channel. - For a bulk transaction, deduplicate affected scopes and channel IDs. Publish per affected scope/channel, never per user. Example: a bulk transaction changes XP for 200 users and voice time in channels `111` and `222`. After commit, publish three events: one XP event, one VC event for `111`, and one VC event for `222`. ## Error handling Publishing is best-effort. Log Redis failures with enough context to identify the event scope, but do not roll back or report a successfully committed database transaction as failed. Open pages recover on SSE reconnection or the next navigation.