// Stack

Tech Stack

discord.js v14.15 sql.js v1.12 Node.js dotenv v16.4 express v4.19 cors v2.8

Key Design Decisions

// Code Structure

File Layout

index.js
Bot entry point. Discord.js client setup, prefix commands (!sync/!unsync), interaction routing (slash, button, modal), appeal button handler, unhandled rejection hooks.
database.js
Complete data layer. sql.js wrapper (Stmt class mimicking better-sqlite3 API), 15+ table schema, all CRUD operations, wait time estimation algorithm, expiry checks.
queue.js
30-second interval queue processor. Matches waiting players to available testers per category. Sends conflict check DMs, notifies channel on assignment.
interactions.js
Button and modal handlers for the testing GUI. Conflict accept/recuse, result submission modal (tier, strength, weakness, recording), recusal reason modal, DM notifications.
announcements.js
Scheduled message reposting. CST timezone parsing, 60s checker, missed announcement DM recovery with Post Now/Skip buttons.
deploy-commands.js
Registers all slash commands to Discord as guild commands. Standalone script (npm run deploy). Reads role config for custom name formatting.

commands/  →  test.js (player)  ·  tester.js (tester)  ·  setup.js (admin roles)  ·  announce.js (scheduling)

// Data Flow

How Data Moves

Testing Lifecycle

1. Player runs /test request → database.js:addToQueue()
2. queue.js:processQueue() runs every 30s → finds match
3. queue.js:sendConflictCheck() → DMs tester with Accept/Recuse
4. interactions.js:handleButton('conflict_no_*') → starts session
5. interactions.js:handleModal('result_modal_*') → saves result
6. database.js:setTier(), updatePairHistory(), setCooldown()
7. Player DMed result → can appeal within 24h via /test appeal
8. Appeal sent to panel channel → uphold/deny → probation check

Database Persistence

All writes go through the Stmt class which wraps sql.js prepared statements. After every .run() call, the entire SQLite database is exported to disk as a binary blob. This ensures data survives bot restarts even without native file locking (sql.js operates entirely in memory and serializes to disk).

// Database

Full Schema

16 SQLite tables power the entire tier system. All stored in data/tiers.db — a single file, auto-created on first run.

players

id TEXT PK, username TEXT
Discord user registry

tiers

player_id TEXT, category TEXT, tier TEXT
Tier placements per player per category

testers

player_id PK, categories JSON, is_junior, available, tests_today, total_tests, upheld_appeals, on_probation, probation_until, ...
Tester registry with stats and probation tracking

test_queue

id PK, player_id, category, priority, requested_at, status
Waiting queue for each category

test_sessions

id PK, player_id, tester_id, category, status, result_tier, strength, weakness, recording_link, flagged, requires_review
Complete test session records

appeals

id PK, session_id, player_id, reason, status, filed_at, reviewed_at, reviewed_by, outcome_tier
Appeal records with panel review tracking

cooldowns

player_id, category, type, expires_at
Per-player cooldowns (retest, etc.)

recusals

id PK, tester_id, player_id, session_id, month
Conflict recusal log for tester accountability

pair_history

tester_id, player_id, category, last_test
Prevents same tester-player pairing back-to-back

ladder

player_id, category, tier, wins, losses, last_active
Ranked ladder per category

challenges

id PK, challenger_id, opponent_id, category, status, expires_at, winner_id, disputed
PvP challenge matches with dual submission

bridge_requests

id PK, player_id, category, current_tier, target_tier, opponent_id, status, winner_id
Tier advancement bridge matches

internal_fights

id PK, tester1_id, tester2_id, winner_id, category, month
Tester-vs-tester match records

flags

id PK, type, reference_id, target_id, reason, resolved
General flagging system for disputes and issues

open_queues

tester_id, category, opened_at
Tracks which testers currently have queues open

wait_time_samples

id PK, category, duration_seconds, recorded_at
Training data for the EWMA wait time predictor

// Internals

Tier Indexing

How Tiers Are Ordered

Tiers are stored as an ordered array where index increases with skill:

Index:  0     1     2     3     4     5     6     7     8     9    10    11    12    13    14
Tier:  LT5   MT5   HT5   LT4   MT4   HT4   LT3   MT3   HT3   LT2   MT2   HT2   LT1   MT1   HT1
       ───────────── lowest ──────────────→────── middle ────────→────── highest ────────────