Files
healer-man/scripts/dungeon-pipeline/audit-boss-party-pressure.ts
T
2026-09-16 14:30:58 -04:00

80 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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));