#!/usr/bin/env node import { writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { BOSS_PRESSURE_PARTY_SIZE, BOSS_PRESSURE_WINDOW_MS, rankBossPartyPressure, } from "../../src/game/bossPressure"; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const playerLevel = Math.max(1, Math.min(80, Number(process.env.BOSS_PRESSURE_LEVEL) || 80)); const { DUNGEON_DEFINITIONS } = await import("../../src/game/dungeonRegistry"); const wowDefinitions = DUNGEON_DEFINITIONS.filter((dungeon) => dungeon.expansion !== "runewaker"); const ranking = rankBossPartyPressure(wowDefinitions, playerLevel); const median = ranking.length ? ranking[Math.floor(ranking.length / 2)].partyDamagePerSecond : 0; const entries = ranking.map((entry, index) => ({ rank: index + 1, pressureVsMedian: median > 0 ? entry.partyDamagePerSecond / median : 0, ...entry, })); const eck = entries.find((entry) => entry.serverEntry === 29_932) ?? null; const report = { schemaVersion: 1, generatedAt: new Date().toISOString(), assumptions: { playerLevel, enemyLevelOffset: 2, durationSeconds: BOSS_PRESSURE_WINDOW_MS / 1_000, partySize: BOSS_PRESSURE_PARTY_SIZE, partyLayout: "all five actors inside nearby-party effects", metric: "unmitigated direct damage from the live boss attack catalog", excludes: [ "armor and resistance", "avoidable movement and interrupts", "summoned creature damage", "damage-amplifying debuffs", "healing and absorbs", "mutually exclusive phase uptime", ], }, bosses: entries.length, medianPartyDamagePerSecond: median, eck, entries, }; const number = (value: number): string => Math.round(value).toLocaleString("en-US"); const decimal = (value: number): string => value.toFixed(1); const top = entries.slice(0, 30); const markdown = `# WoW boss party-pressure audit Generated ${report.generatedAt}. This is a deterministic comparison at player level ${playerLevel}: ${BOSS_PRESSURE_PARTY_SIZE} grouped party members, ${BOSS_PRESSURE_WINDOW_MS / 1_000} seconds, and live boss attack definitions. It is a pressure ceiling, not a combat-log prediction: mitigation, movement, interrupts, summons, debuff amplification, and phase uptime are excluded. | Rank | Boss | Dungeon | Party DPS | Tank DPS | Random DPS | Groupwide DPS | vs median | | ---: | --- | --- | ---: | ---: | ---: | ---: | ---: | ${top.map((entry) => `| ${entry.rank} | ${entry.boss} | ${entry.dungeonTitle} | ${number(entry.partyDamagePerSecond)} | ${number(entry.tankDamage / entry.windowSeconds)} | ${number(entry.randomTargetDamage / entry.windowSeconds)} | ${number(entry.groupwideDamage / entry.windowSeconds)} | ${decimal(entry.pressureVsMedian)}x |`).join("\n")} ## Eck the Ferocious ${eck ? `Eck ranks **${eck.rank} of ${entries.length}** at **${number(eck.partyDamagePerSecond)} raw party DPS** (**${decimal(eck.pressureVsMedian)}x** the median). His corrected schedule contains one 2-second melee swing, Bite every 8–12 seconds, Spit every 8–12 seconds after its delayed opener, Spring every 10–24 seconds against a random party member, and a non-damaging Berserk at 90 seconds.` : "Eck was not found in the runtime catalog."} The complete machine-readable ranking and per-attack breakdown are in \`dungeon-pipeline/WOW_BOSS_PRESSURE_REPORT.json\`. `; const jsonPath = path.join(projectRoot, "dungeon-pipeline", "WOW_BOSS_PRESSURE_REPORT.json"); const markdownPath = path.join(projectRoot, "dungeon-pipeline", "WOW_BOSS_PRESSURE_REPORT.md"); await Promise.all([ writeFile(jsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"), writeFile(markdownPath, markdown, "utf8"), ]); console.log(JSON.stringify({ status: "complete", bosses: entries.length, playerLevel, medianPartyDamagePerSecond: Number(median.toFixed(2)), eck: eck ? { rank: eck.rank, partyDamagePerSecond: Number(eck.partyDamagePerSecond.toFixed(2)), pressureVsMedian: Number(eck.pressureVsMedian.toFixed(2)) } : null, outputs: [path.relative(projectRoot, jsonPath), path.relative(projectRoot, markdownPath)], }, null, 2));