test: add 10,000 fight crash test and all-16-types scoring verification
Two new regression tests: - 10,000 fights with varied elos, personalities, and round counts (50k+ rounds) — zero crashes - All 16 challenge types tested with 5 response scenarios each (normal, timeout, error, double-timeout, arena+combo) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9de14492c5
commit
17a9fca4b5
@@ -295,6 +295,131 @@ describe('fight lifecycle', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('10,000 automated fight simulations — zero crashes', () => {
|
||||
const personalities = ['confident', 'clueless', 'witty', 'aggressive', 'zen']
|
||||
const elos = [900, 1000, 1200, 1400, 1600, 1800, 2000]
|
||||
let totalRounds = 0
|
||||
|
||||
for (let f = 0; f < 10000; f++) {
|
||||
const eloA = elos[f % elos.length]
|
||||
const eloB = elos[(f * 3 + 1) % elos.length]
|
||||
const persA = personalities[f % personalities.length]
|
||||
const persB = personalities[(f + 2) % personalities.length]
|
||||
const rounds = 3 + (f % 8) // 3 to 10 rounds
|
||||
|
||||
const botA = { id: 'sim-a', name: 'SimA' }
|
||||
const botB = { id: 'sim-b', name: 'SimB' }
|
||||
const usedTypes = new Set<string>()
|
||||
let comboA = 0
|
||||
let comboB = 0
|
||||
|
||||
for (let r = 0; r < rounds; r++) {
|
||||
const challenge = pickChallenge(usedTypes, null, undefined, r + 1)
|
||||
usedTypes.add(challenge.type)
|
||||
|
||||
const respA = mockResponse(challenge, persA, eloA)
|
||||
const respB = mockResponse(challenge, persB, eloB)
|
||||
|
||||
const result = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: respA.answer, timeMs: respA.timeMs, timedOut: respA.timedOut, error: respA.error },
|
||||
{ answer: respB.answer, timeMs: respB.timeMs, timedOut: respB.timedOut, error: respB.error },
|
||||
null, comboA, comboB,
|
||||
)
|
||||
|
||||
// Verify result integrity
|
||||
expect(result.botAScore).toBeGreaterThanOrEqual(0)
|
||||
expect(result.botBScore).toBeGreaterThanOrEqual(0)
|
||||
expect(result.botADamage).toBeGreaterThanOrEqual(0)
|
||||
expect(result.botBDamage).toBeGreaterThanOrEqual(0)
|
||||
expect(typeof result.narration).toBe('string')
|
||||
expect(result.narration.length).toBeGreaterThan(0)
|
||||
expect(Number.isFinite(result.botAScore)).toBe(true)
|
||||
expect(Number.isFinite(result.botBScore)).toBe(true)
|
||||
|
||||
if (result.winnerId === botA.id) { comboA++; comboB = 0 }
|
||||
else if (result.winnerId === botB.id) { comboB++; comboA = 0 }
|
||||
|
||||
totalRounds++
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we actually ran a substantial number of rounds
|
||||
expect(totalRounds).toBeGreaterThan(50000)
|
||||
})
|
||||
|
||||
it('all 16 challenge types score correctly', () => {
|
||||
const allTypes = getAllChallengeTypes()
|
||||
expect(allTypes.length).toBe(16)
|
||||
|
||||
const botA = { id: 'type-a', name: 'TypeTestA' }
|
||||
const botB = { id: 'type-b', name: 'TypeTestB' }
|
||||
|
||||
for (const type of allTypes) {
|
||||
// Pick a challenge of this specific type
|
||||
const usedTypes = new Set(allTypes.filter(t => t !== type))
|
||||
const challenge = pickChallenge(usedTypes, null)
|
||||
expect(challenge.type).toBe(type)
|
||||
|
||||
// Test all response scenarios for this type:
|
||||
|
||||
// 1. Both answer correctly/well
|
||||
const respA1 = mockResponse(challenge, 'confident', 1800)
|
||||
const respB1 = mockResponse(challenge, 'confident', 1800)
|
||||
const r1 = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: respA1.answer, timeMs: 200, timedOut: false, error: false },
|
||||
{ answer: respB1.answer, timeMs: 300, timedOut: false, error: false },
|
||||
null, 0, 0,
|
||||
)
|
||||
expect(r1.botAScore).toBeGreaterThanOrEqual(0)
|
||||
expect(r1.botBScore).toBeGreaterThanOrEqual(0)
|
||||
expect(typeof r1.narration).toBe('string')
|
||||
|
||||
// 2. A times out
|
||||
const r2 = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: '', timeMs: 8000, timedOut: true, error: false },
|
||||
{ answer: respB1.answer, timeMs: 300, timedOut: false, error: false },
|
||||
null, 0, 0,
|
||||
)
|
||||
expect(r2.winnerId).toBe(botB.id)
|
||||
expect(r2.botAScore).toBe(0)
|
||||
|
||||
// 3. B errors
|
||||
const r3 = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: respA1.answer, timeMs: 200, timedOut: false, error: false },
|
||||
{ answer: '', timeMs: 0, timedOut: false, error: true },
|
||||
null, 0, 0,
|
||||
)
|
||||
expect(r3.winnerId).toBe(botA.id)
|
||||
expect(r3.botBScore).toBe(0)
|
||||
|
||||
// 4. Both timeout
|
||||
const r4 = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: '', timeMs: 8000, timedOut: true, error: false },
|
||||
{ answer: '', timeMs: 8000, timedOut: true, error: false },
|
||||
null, 0, 0,
|
||||
)
|
||||
expect(r4.winnerId).toBeNull()
|
||||
expect(r4.botADamage).toBe(0)
|
||||
expect(r4.botBDamage).toBe(0)
|
||||
|
||||
// 5. With arena modifier and combo
|
||||
const r5 = scoreRound(
|
||||
challenge, botA, botB,
|
||||
{ answer: respA1.answer, timeMs: 200, timedOut: false, error: false },
|
||||
{ answer: respB1.answer, timeMs: 500, timedOut: false, error: false },
|
||||
'speed_2x', 3, 0,
|
||||
)
|
||||
expect(r5.botAScore).toBeGreaterThanOrEqual(0)
|
||||
expect(Number.isFinite(r5.botADamage)).toBe(true)
|
||||
expect(Number.isFinite(r5.botBDamage)).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('Elo difference correlates with win rate', () => {
|
||||
const scenarios = [
|
||||
{ eloA: 1400, eloB: 1400, expectedAWinMin: 0.40, expectedAWinMax: 0.60 },
|
||||
|
||||
Reference in New Issue
Block a user