import assert from "node:assert/strict"; import test from "node:test"; import { ARENA_RATING_BANDS, arenaAiProfile, arenaDampening, arenaDrDuration, arenaMatchTolerance, calculateArenaRating, shouldArenaBotChooseCorrectly, validateArenaComposition, } from "./arena-rules.mjs"; function seededRandom(seed) { let value = seed >>> 0; return () => { value = (value * 1664525 + 1013904223) >>> 0; return value / 0x100000000; }; } test("arena Elo uses band K values, draws are neutral, and clamps at both ends", () => { assert.deepEqual(calculateArenaRating({ rating: 1000, opponentRating: 1000, score: 1 }), { before: 1000, expected: 0.5, delta: 20, after: 1020, }); assert.equal(calculateArenaRating({ rating: 2499, opponentRating: 2500, score: 1 }).after, 2500); assert.equal(calculateArenaRating({ rating: 0, opponentRating: 0, score: 0 }).after, 0); assert.equal(calculateArenaRating({ rating: 1600, opponentRating: 1900, score: 0.5 }).delta, 0); }); test("arena composition requires exactly one healer and rejects tanks", () => { assert.equal(validateArenaComposition("2v2", ["healer", "damage"]).ok, true); assert.equal(validateArenaComposition("3v3", ["healer", "damage", "damage"]).ok, true); assert.equal(validateArenaComposition("3v3", ["healer"], { allowPartial: true }).missing.damage, 2); assert.equal(validateArenaComposition("2v2", ["tank", "damage"]).code, "invalid_arena_role"); assert.equal(validateArenaComposition("2v2", ["damage", "damage"]).code, "invalid_composition"); }); test("difficulty, dampening, matchmaking, and DR follow the rated rules", () => { const gladiator = arenaAiProfile(2500); assert.equal(gladiator.healerInterruptChance, 0.85); assert.equal(gladiator.minimumErrorChance, 0.08); assert.equal(shouldArenaBotChooseCorrectly(1, gladiator.minimumErrorChance, () => 0.93), false); assert.equal(arenaDampening(0), 0.1); assert.equal(arenaDampening(540_000), 1); assert.equal(arenaMatchTolerance(0), 100); assert.equal(arenaMatchTolerance(30_000), 400); assert.equal(arenaDrDuration(8000, 2, 0).durationMs, 2000); assert.equal(arenaDrDuration(8000, 3, 15_000).durationMs, 8000); }); test("every AI band honors its settings and seeded minimum-error floor", () => { for (const [index, band] of ARENA_RATING_BANDS.entries()) { const profile = arenaAiProfile(band.min); assert.equal(profile.title, band.title); assert.equal(profile.reactionMinSeconds, band.reaction[0]); assert.equal(profile.reactionMaxSeconds, band.reaction[1]); assert.equal(profile.healerInterruptChance, band.healerInterrupt); assert.equal(profile.coordinatedCcChance, band.coordinatedCc); assert.equal(profile.correctDefensiveChance, band.defensive); assert.equal(profile.minimumErrorChance, band.error); const random = seededRandom(index + 1); const decisions = Array.from({ length: 4000 }, () => shouldArenaBotChooseCorrectly(1, profile.minimumErrorChance, random)); const errorRate = decisions.filter((decision) => !decision).length / decisions.length; assert.ok(errorRate >= profile.minimumErrorChance - 0.025, `${band.title} fell below its minimum-error floor`); assert.ok(errorRate <= profile.minimumErrorChance + 0.025, `${band.title} exceeded its seeded error tolerance`); } });