84 lines
2.8 KiB
Markdown
84 lines
2.8 KiB
Markdown
# 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.
|