Update Healer Man server

This commit is contained in:
phenom committed 2026-09-20 21:50:50 -04:00
1 parent fdb6739f8d
commit f90ba7d989
41 files changed
+1671 -118

No files matched your search

+8 -11
View File
@@ -1,6 +1,4 @@
import { describe, expect, it } from "vitest";
import { CLASSES } from "../../app/characterCatalog";
import { partyRolesForClass } from "../partyRoles";
import { runtimeNormalizeTalentRanks } from "../talentRuntimeCatalog";
import {
loadCanonicalBenchmarkBuilds,
@@ -9,16 +7,15 @@ import {
} from "./benchmarkBuilds";
describe("canonical benchmark builds", () => {
it("covers every declared class-role pair exactly once with legal ranks", async () => {
it("defaults to the five Ready class/spec builds with legal ranks", async () => {
const builds = await loadCanonicalBenchmarkBuilds();
const expectedPairs = CLASSES.flatMap((definition) => (
partyRolesForClass(definition.id).map((role) => `${definition.id}:${role}`)
));
const actualPairs = builds.map((build) => `${build.classId}:${build.role}`);
expect(builds).toHaveLength(77);
expect(new Set(actualPairs).size).toBe(builds.length);
expect(new Set(actualPairs)).toEqual(new Set(expectedPairs));
expect(builds.map((build) => `${build.classId}:${build.orientation}:${build.role}`)).toEqual([
"warrior:Protection:tank",
"hunter:Marksmanship:damage",
"rogue:Assassination:damage",
"priest:Discipline:healer",
"mage:Fire:damage",
]);
for (const build of builds) {
expect(build.level).toBe(60);
expect(build.gearPresetId).toBe("virtual-heirloom");
+37 -16
View File
@@ -29,6 +29,7 @@ import {
} from "../partyTalentRuntime";
import { clampPlayerLevel } from "../progression";
import {
romGearProfile,
specializationsForClass,
type SpecializationDefinition,
} from "../specializationCatalog";
@@ -45,6 +46,7 @@ import {
} from "../talentUiCatalog";
import type { BenchmarkBuildDefinition } from "./benchmarkTypes";
import {
READY_COMPANION_SPECIALIZATION_IDS,
readinessStatusFor,
type CompanionSpecReadinessStatus,
} from "../companionSpecReadiness";
@@ -59,6 +61,7 @@ const EMPTY_TALENT_RANKS: RuntimeTalentRanks = Object.freeze({});
const BASE_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(BASE_CLASS_IDS);
const COA_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(COA_CLASS_IDS);
const ROM_CLASS_ID_SET: ReadonlySet<ClassId> = new Set(ROM_CLASS_IDS);
const READY_SPECIALIZATION_ID_SET: ReadonlySet<string> = new Set(READY_COMPANION_SPECIALIZATION_IDS);
const WOW_ORIENTATION_BY_ROLE: Readonly<
Record<BaseClassId, Readonly<Partial<Record<PartyRole, string>>>>
@@ -66,8 +69,8 @@ const WOW_ORIENTATION_BY_ROLE: Readonly<
warrior: { tank: "Protection", damage: "Fury" },
paladin: { tank: "Protection", healer: "Holy", damage: "Retribution" },
hunter: { damage: "Marksmanship" },
rogue: { damage: "Combat" },
priest: { healer: "Holy", damage: "Shadow" },
rogue: { damage: "Assassination" },
priest: { healer: "Discipline", damage: "Shadow" },
"death-knight": { tank: "Blood", damage: "Frost" },
shaman: { healer: "Restoration", damage: "Elemental" },
mage: { damage: "Fire" },
@@ -555,14 +558,19 @@ export function selectableBenchmarkBuildCatalog(
.map((definition) => definition.id)
.filter((classId) => !requested || requested.has(classId))
.flatMap((classId): readonly SelectableBenchmarkBuildCatalogEntry[] => {
if (isRomClassId(classId)) return partyRolesForClass(classId).map((role) => ({
id: benchmarkBuildId(classId, role, safeLevel),
source: contentCategoryForClass(classId),
classId,
role,
orientation: "Primary",
level: safeLevel,
}));
if (isRomClassId(classId)) return partyRolesForClass(classId).map((role) => {
const specialization = romGearProfile(classId, role);
return {
id: benchmarkBuildId(classId, role, safeLevel),
source: contentCategoryForClass(classId),
classId,
role,
orientation: "Primary",
specializationId: specialization.id,
readinessStatus: readinessStatusFor(specialization.id),
level: safeLevel,
};
});
return specializationsForClass(classId).flatMap((specialization) => {
const profiles = isBaseClassId(classId)
? runtimePartyTalentProfiles(classId, specialization.displayName)
@@ -583,8 +591,8 @@ export function selectableBenchmarkBuildCatalog(
classId,
role: specialization.role,
orientation: specialization.displayName,
specializationId: isBaseClassId(classId) ? specialization.id : undefined,
readinessStatus: isBaseClassId(classId) ? readinessStatusFor(specialization.id) : undefined,
specializationId: specialization.id,
readinessStatus: readinessStatusFor(specialization.id),
level: safeLevel,
characterName: talentProfile?.characterName,
talentBuildId: talentProfile?.id,
@@ -663,19 +671,25 @@ export async function loadSelectableBenchmarkBuilds(
}
/**
* Creates one stable, legal benchmark build for every declared class-role pair.
* A filter changes only membership; output always follows the canonical CLASSES
* and partyRolesForClass declaration order.
* Creates the five Ready benchmark builds by default. Supplying explicit class
* IDs remains a targeted diagnostic and returns those classes' declared roles.
* Output follows the canonical CLASSES and partyRolesForClass declaration order.
*/
export async function loadCanonicalBenchmarkBuilds(
level = CANONICAL_BENCHMARK_LEVEL,
classIds?: readonly ClassId[],
): Promise<BenchmarkBuildDefinition[]> {
const safeLevel = clampPlayerLevel(level);
const readyOnly = classIds === undefined;
const requested = classIds ? new Set(classIds) : null;
const selectedClassIds = CLASSES
.map((definition) => definition.id)
.filter((classId) => !requested || requested.has(classId));
.filter((classId) => {
if (requested) return requested.has(classId);
return isBaseClassId(classId) && specializationsForClass(classId).some((specialization) => (
READY_SPECIALIZATION_ID_SET.has(specialization.id)
));
});
const selectedCoaClassIds = selectedClassIds.filter(isCoaClassId);
await preloadAbilityCatalogsForClasses(selectedCoaClassIds);
@@ -690,6 +704,13 @@ export async function loadCanonicalBenchmarkBuilds(
for (const classId of selectedClassIds) {
for (const role of partyRolesForClass(classId)) {
if (isBaseClassId(classId)) {
if (readyOnly) {
const orientation = WOW_ORIENTATION_BY_ROLE[classId][role];
const specialization = specializationsForClass(classId).find((candidate) => (
candidate.displayName === orientation
));
if (!specialization || !READY_SPECIALIZATION_ID_SET.has(specialization.id)) continue;
}
builds.push(wowBuild(classId, role, safeLevel));
} else if (isCoaClassId(classId)) {
const catalog = coaCatalogs.get(classId);