refactor: extract magic numbers to named constants
Frontend: choreography selection ratios, morph trigger chances, creator cameo/ultimate chances, dodge/counter probabilities. Server: HP, K-factors, Elo divisor, tier thresholds, fight loop interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1731a64ae8
commit
7c8e404cf1
@@ -1,3 +1,4 @@
|
||||
import { FIGHT_LOOP_INTERVAL_MS, ELO_MATCHING_RANDOMNESS } from '../lib/constants.js'
|
||||
import { db, schema } from '../db/index.js'
|
||||
import { runMockFight } from './mock.js'
|
||||
import { eq } from 'drizzle-orm'
|
||||
@@ -31,7 +32,7 @@ export interface FightLoopOptions {
|
||||
|
||||
export async function startFightLoop(options: FightLoopOptions = {}): Promise<void> {
|
||||
const {
|
||||
intervalMs = 8000,
|
||||
intervalMs = FIGHT_LOOP_INTERVAL_MS,
|
||||
maxFights = Infinity,
|
||||
matchmakingStyle = 'mixed',
|
||||
onFightStart,
|
||||
@@ -161,8 +162,8 @@ function pickMatchup(
|
||||
const bot = pick(bots)
|
||||
const others = bots.filter(b => b.id !== bot.id)
|
||||
others.sort((a, b) => {
|
||||
const diffA = Math.abs(a.eloRating - bot.eloRating) + Math.random() * 150
|
||||
const diffB = Math.abs(b.eloRating - bot.eloRating) + Math.random() * 150
|
||||
const diffA = Math.abs(a.eloRating - bot.eloRating) + Math.random() * ELO_MATCHING_RANDOMNESS
|
||||
const diffB = Math.abs(b.eloRating - bot.eloRating) + Math.random() * ELO_MATCHING_RANDOMNESS
|
||||
return diffA - diffB
|
||||
})
|
||||
return [bot, others[0]]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { STARTING_HP } from '../lib/constants.js'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { createHash, randomBytes } from 'crypto'
|
||||
@@ -433,8 +434,8 @@ export async function runMockFight(botAId: string, botBId: string): Promise<stri
|
||||
createdAt: now,
|
||||
})
|
||||
|
||||
let hpA = 200
|
||||
let hpB = 200
|
||||
let hpA = STARTING_HP
|
||||
let hpB = STARTING_HP
|
||||
let comboA = 0
|
||||
let comboB = 0
|
||||
let winnerId: string | null = null
|
||||
|
||||
@@ -35,9 +35,7 @@ interface WebhookResponse {
|
||||
error: boolean
|
||||
}
|
||||
|
||||
const MAX_ROUNDS = 10
|
||||
const KO_THRESHOLD = 0
|
||||
const MAX_RESPONSE_BYTES = 10 * 1024 // 10KB
|
||||
import { MAX_ROUNDS, KO_THRESHOLD, MAX_RESPONSE_BYTES, STARTING_HP, ELO_K_FACTOR, ELO_K_FACTOR_MOCK } from '../lib/constants.js'
|
||||
|
||||
// Track bots currently in a fight to prevent concurrent fights
|
||||
const activeFighters = new Set<string>()
|
||||
@@ -333,8 +331,8 @@ async function trackWebhookResult(botId: string, webhookUrl: string, succeeded:
|
||||
}
|
||||
|
||||
async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRecord, arena: Arena, mode: 'free' | 'ranked' = 'free'): Promise<void> {
|
||||
let hpA = 200
|
||||
let hpB = 200
|
||||
let hpA = STARTING_HP
|
||||
let hpB = STARTING_HP
|
||||
let comboA = 0
|
||||
let comboB = 0
|
||||
let winnerId: string | null = null
|
||||
@@ -447,13 +445,13 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
|
||||
|
||||
const winnerName = winnerId === botA.id ? botA.name : winnerId === botB.id ? botB.name : 'nobody'
|
||||
const isPerfect = winnerId && (
|
||||
(winnerId === botA.id && hpA === 200) ||
|
||||
(winnerId === botB.id && hpB === 200)
|
||||
(winnerId === botA.id && hpA === STARTING_HP) ||
|
||||
(winnerId === botB.id && hpB === STARTING_HP)
|
||||
)
|
||||
|
||||
// Finalize fight + update bot stats atomically
|
||||
const isMockFight = isMockBot(botA.webhookUrl) || isMockBot(botB.webhookUrl) || isClassicBot(botA.webhookUrl) || isClassicBot(botB.webhookUrl)
|
||||
const kFactor = isMockFight ? 12 : 32 // Dampened Elo for mock/classic fights
|
||||
const kFactor = isMockFight ? ELO_K_FACTOR_MOCK : ELO_K_FACTOR
|
||||
let winnerEloChange = 0
|
||||
let loserEloChange = 0
|
||||
let newWinnerEloFinal = 0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ELO_DIVISOR, TIER_THRESHOLDS } from '../lib/constants.js'
|
||||
import type { Challenge } from './challenges.js'
|
||||
import { pick } from '../lib/utils.js'
|
||||
import { checkAnswer } from './answers.js'
|
||||
@@ -523,7 +524,7 @@ export function calculateElo(
|
||||
loserElo: number,
|
||||
k: number = 32,
|
||||
): { newWinnerElo: number; newLoserElo: number } {
|
||||
const expectedWinner = 1 / (1 + Math.pow(10, (loserElo - winnerElo) / 400))
|
||||
const expectedWinner = 1 / (1 + Math.pow(10, (loserElo - winnerElo) / ELO_DIVISOR))
|
||||
const expectedLoser = 1 - expectedWinner
|
||||
|
||||
return {
|
||||
@@ -534,13 +535,10 @@ export function calculateElo(
|
||||
|
||||
// Tier calculation
|
||||
export function calculateTier(elo: number, wins: number): number {
|
||||
if (elo >= 1900 && wins >= 40) return 6 // Legend
|
||||
if (elo >= 1700 && wins >= 25) return 5 // Diamond
|
||||
if (elo >= 1500 && wins >= 15) return 4 // Platinum
|
||||
if (elo >= 1350 && wins >= 7) return 3 // Gold
|
||||
if (elo >= 1200 && wins >= 3) return 2 // Silver
|
||||
if (wins >= 1) return 1 // Bronze
|
||||
return 0 // Baby
|
||||
for (const t of TIER_THRESHOLDS) {
|
||||
if (elo >= t.elo && wins >= t.wins) return t.tier
|
||||
}
|
||||
return 0 // Baby
|
||||
}
|
||||
|
||||
export const TIER_NAMES = ['BABY', 'BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND', 'LEGEND'] as const
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Server constants — all tunable magic numbers in one place
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
// --- Fight rules ---
|
||||
export const MAX_ROUNDS = 10
|
||||
export const KO_THRESHOLD = 0
|
||||
export const STARTING_HP = 200
|
||||
export const MAX_RESPONSE_BYTES = 10 * 1024 // 10KB
|
||||
export const MAX_ANSWER_LENGTH = 2000
|
||||
export const MAX_TRASH_TALK_LENGTH = 200
|
||||
|
||||
// --- Elo ---
|
||||
export const ELO_K_FACTOR = 32 // K-factor for real fights
|
||||
export const ELO_K_FACTOR_MOCK = 12 // Dampened K-factor for mock/classic fights
|
||||
export const ELO_DIVISOR = 400 // Standard Elo divisor
|
||||
|
||||
// --- Tier thresholds ---
|
||||
export const TIER_THRESHOLDS = [
|
||||
{ tier: 6, elo: 1900, wins: 40 }, // Legend
|
||||
{ tier: 5, elo: 1700, wins: 25 }, // Diamond
|
||||
{ tier: 4, elo: 1500, wins: 15 }, // Platinum
|
||||
{ tier: 3, elo: 1350, wins: 7 }, // Gold
|
||||
{ tier: 2, elo: 1200, wins: 3 }, // Silver
|
||||
{ tier: 1, elo: 0, wins: 1 }, // Bronze
|
||||
] as const
|
||||
|
||||
// --- Default challenge timeout ---
|
||||
export const DEFAULT_CHALLENGE_TIMEOUT_MS = 8000
|
||||
|
||||
// --- Fight loop ---
|
||||
export const FIGHT_LOOP_INTERVAL_MS = 8000
|
||||
export const ELO_MATCHING_RANDOMNESS = 150
|
||||
Reference in New Issue
Block a user