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:
Dorian
2026-03-08 23:34:05 +00:00
co-authored by Claude Opus 4.6
parent 1731a64ae8
commit 7c8e404cf1
8 changed files with 129 additions and 45 deletions
+43
View File
@@ -0,0 +1,43 @@
// ═══════════════════════════════════════════════════════════════
// Gameplay constants — all tunable magic numbers in one place
// ═══════════════════════════════════════════════════════════════
// --- Choreography selection probabilities ---
export const CREATOR_ULTIMATE_CHANCE = 0.5 // Creator ultimate chance (non-crit); 1.0 on crits
export const CREATOR_THEMED_MOVE_RATIO = 0.6 // Creator uses creator-themed moves 60% of the time
export const CRIT_THEMED_RATIO = 0.5 // On crits: 50% themed, 50% epic generic
export const CHOREOGRAPHY_THEMED_RATIO = 0.6 // Normal: 60% themed
export const CHOREOGRAPHY_GENERIC_CUTOFF = 0.85 // Normal: 25% generic (0.600.85), 15% wild (0.851.0)
// --- Tier-gated ultimate chances ---
export const TIER_ULTIMATE_CHANCES: Record<number, number> = {
2: 0.15,
3: 0.20,
4: 0.30,
5: 0.40,
}
// --- Morph system ---
export const CREATOR_MORPH_CRIT_CHANCE = 0.7 // Creator morphs on devastating crits 70%
export const CREATOR_MORPH_INTENSITY_THRESHOLD = 0.6 // Creator morphs when intensity > 0.6
export const BOT_MORPH_CRIT_CHANCE = 0.4 // Regular bots morph on devastating crits 40%
export const BOT_MORPH_INTENSITY_THRESHOLD = 0.7 // Regular bots morph when intensity > 0.7
// --- Creator cameo ---
export const CREATOR_CAMEO_CHANCE = 0.06 // 6% per round in non-creator fights
// --- Round exchange probabilities ---
export const DODGE_CHANCE = 0.12 // Defender dodges instead of being hit
export const COUNTER_ATTACK_CHANCE = 0.25 // Defender counters after getting hit
export const SCANLINE_GLITCH_CHANCE = 0.25 // Random scanline glitch during exchanges
export const COMEDY_SFX_CHANCE = 0.15 // Comedy sound on non-decisive hits
export const PHYSICAL_CONTACT_BASE = 0.35 // Base chance for brawl/clinch
export const PHYSICAL_CONTACT_INTENSITY_SCALE = 0.2 // Added per unit of intensity
export const PHYSICAL_CONTACT_EXCHANGE_BONUS = 0.15 // Added after first exchange
export const CROWD_REACTION_CHANCE = 0.4 // Crowd reacts to round results
export const MAYBE_SHOW_HUMAN_CHANCE = 0.2 // Show human coach
export const CREATOR_VOICE_LINE_CHANCE = 0.6 // Creator gets a voice line
// --- Hyperdetail (grotesque close-up) ---
export const HYPERDETAIL_BASE_CHANCE = 0.05 // Base hyperdetail chance
export const HYPERDETAIL_INTENSITY_SCALE = 0.45 // Scales with intensity
+22 -13
View File
@@ -10,6 +10,15 @@ import {
sfxDrumRoll, sfxPowerUp, sfxVineBoom, sfxRandomSilly, sfxRandomComedy,
sfxBoneCrack, sfxSlideWhistleDown, sfxDodge, sfxRandomFail, sfxSlideUp,
} from '../audio'
import {
CREATOR_MORPH_CRIT_CHANCE, CREATOR_MORPH_INTENSITY_THRESHOLD,
BOT_MORPH_CRIT_CHANCE, BOT_MORPH_INTENSITY_THRESHOLD,
CREATOR_CAMEO_CHANCE, DODGE_CHANCE, COUNTER_ATTACK_CHANCE,
SCANLINE_GLITCH_CHANCE, COMEDY_SFX_CHANCE,
PHYSICAL_CONTACT_BASE, PHYSICAL_CONTACT_INTENSITY_SCALE, PHYSICAL_CONTACT_EXCHANGE_BONUS,
CROWD_REACTION_CHANCE, MAYBE_SHOW_HUMAN_CHANCE, CREATOR_VOICE_LINE_CHANCE,
HYPERDETAIL_BASE_CHANCE, HYPERDETAIL_INTENSITY_SCALE,
} from './config'
interface RoundDeps {
HOME_A: number
@@ -691,7 +700,7 @@ async function playRound(event: RoundEvent) {
const roundBonus = Math.min(2, Math.floor(event.round / 2))
const exchangeCount = 3 + roundBonus + Math.floor(Math.random() * 3) + (intensity > 0.5 ? 1 : 0)
// Hyperdetail scales with intensity
const hyperDetail = Math.random() < (0.05 + intensity * 0.45)
const hyperDetail = Math.random() < (HYPERDETAIL_BASE_CHANCE + intensity * HYPERDETAIL_INTENSITY_SCALE)
const savedScaleAX = fA?.scale.x
const savedScaleAY = fA?.scale.y
const savedScaleBX = fB?.scale.x
@@ -702,7 +711,7 @@ async function playRound(event: RoundEvent) {
let didChallengeVoice = false
if (Math.random() < (creatorInFight ? 0.5 : 0.3)) {
// Creator fights get existential commentary more often
if (creatorInFight && Math.random() < 0.6) {
if (creatorInFight && Math.random() < CREATOR_VOICE_LINE_CHANCE) {
announceCreatorRound(); didChallengeVoice = true
} else {
const challengeVoice: Record<string, () => void> = {
@@ -883,7 +892,7 @@ async function playRound(event: RoundEvent) {
attackerSide = Math.random() < Math.max(0.1, 0.4 - intensity * 0.25) ? loserSide : winnerSide
exchangeCritical = false
// Occasionally play a comedy sound on non-decisive hits
if (Math.random() < 0.15) { Math.random() < 0.5 ? sfxRandomComedy() : sfxRandomSilly() }
if (Math.random() < COMEDY_SFX_CHANCE) { Math.random() < 0.5 ? sfxRandomComedy() : sfxRandomSilly() }
} else {
// Draw: alternate
attackerSide = ex % 2 === 0 ? 'a' : 'b'
@@ -898,8 +907,8 @@ async function playRound(event: RoundEvent) {
const isCreatorFighter = attackerArch === 'the_creator'
// Creator: always morph on ultimates, 70% on devastating crits
const shouldMorph = isCreatorFighter
? (isUltimate || (exchangeCritical && intensity > 0.6 && Math.random() < 0.7))
: (isUltimate || (exchangeCritical && intensity > 0.7 && Math.random() < 0.4))
? (isUltimate || (exchangeCritical && intensity > CREATOR_MORPH_INTENSITY_THRESHOLD && Math.random() < CREATOR_MORPH_CRIT_CHANCE))
: (isUltimate || (exchangeCritical && intensity > BOT_MORPH_INTENSITY_THRESHOLD && Math.random() < BOT_MORPH_CRIT_CHANCE))
let morphRevert: (() => Promise<void>) | null = null
if (shouldMorph) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
@@ -985,7 +994,7 @@ async function playRound(event: RoundEvent) {
}
// Random scanline glitch during exchanges (25%)
if (Math.random() < 0.25) scanlineGlitch(0.2)
if (Math.random() < SCANLINE_GLITCH_CHANCE) scanlineGlitch(0.2)
// Play the exchange — mix of choreography, brawls, clinches for ~50% physical contact
if (!aWon && !bWon && isLastExchange) {
@@ -994,7 +1003,7 @@ async function playRound(event: RoundEvent) {
} else {
// Decide exchange type: choreography, brawl, or clinch
// Brawl/clinch chance increases with later exchanges and higher intensity
const physicalChance = 0.35 + intensity * 0.2 + (ex > 1 ? 0.15 : 0)
const physicalChance = PHYSICAL_CONTACT_BASE + intensity * PHYSICAL_CONTACT_INTENSITY_SCALE + (ex > 1 ? PHYSICAL_CONTACT_EXCHANGE_BONUS : 0)
const exchangeType = Math.random()
if (!isLastExchange && exchangeType < physicalChance * 0.5) {
@@ -1003,17 +1012,17 @@ async function playRound(event: RoundEvent) {
} else if (!isLastExchange && exchangeType < physicalChance) {
// CLINCH COMBO: attacker grapples defender at close range
await _clinchCombo(attackerSide, intensity)
} else if (!isLastExchange && Math.random() < 0.12) {
// DODGE: defender dodges (reduced from 15% to 12% — more contact, fewer misses)
} else if (!isLastExchange && Math.random() < DODGE_CHANCE) {
await playDodge(attackerSide === 'a' ? 'b' : 'a')
sfxDodge()
if (Math.random() < 0.4) sfxRandomFail(); else sfxBoing()
if (Math.random() < 0.4) sfxRandomFail()
else sfxBoing()
} else {
// CHOREOGRAPHY: signature move from the pool
await playAttack(attackerSide, choreo, exchangeCritical)
// COUNTER-ATTACK: defender fights back after getting hit (25% chance on non-final exchanges)
if (!isLastExchange && !exchangeCritical && Math.random() < 0.25) {
if (!isLastExchange && !exchangeCritical && Math.random() < COUNTER_ATTACK_CHANCE) {
await k.wait(0.08)
await _counterAttack(attackerSide === 'a' ? 'b' : 'a')
}
@@ -1070,7 +1079,7 @@ async function playRound(event: RoundEvent) {
// === THE CREATOR CAMEO ===
// 6% chance per round (only if neither fighter IS the creator)
const neitherIsCreator = botA.archetype !== 'the_creator' && botB.archetype !== 'the_creator'
if (neitherIsCreator && Math.random() < 0.06) {
if (neitherIsCreator && Math.random() < CREATOR_CAMEO_CHANCE) {
const cameoX = k.width() / 2
const cameoY = GROUND_Y - 80
// Golden portal flash
@@ -1195,7 +1204,7 @@ async function playRound(event: RoundEvent) {
announceCrowdReaction('cheer')
} else if (aWon || bWon) {
// Regular round win: crowd reacts (40%) — only SFX, no speech
if (Math.random() < 0.4) announceCrowdReaction(Math.random() < 0.5 ? 'cheer' : 'applause')
if (Math.random() < CROWD_REACTION_CHANCE) announceCrowdReaction(Math.random() < 0.5 ? 'cheer' : 'applause')
// Heartfelt announcer moment (10% chance on normal, non-critical rounds)
if (Math.random() < 0.1) {
await k.wait(0.3)