Update Healer Man server
This commit is contained in:
1 parent
1fedb9368d
commit
cc3b2fa4d5
27 files changed
+1988
-83
No files matched your search
+34
-7
@@ -166,9 +166,7 @@ def random_party(builds: Sequence[dict[str, Any]], seed: int) -> list[dict[str,
|
||||
generator = random.Random(seed)
|
||||
# Named alternatives remain directly selectable and are covered by "all",
|
||||
# but ordinary random/filler seats should use the declared default profile.
|
||||
defaults = [build for build in builds if build.get("defaultTalentBuild")]
|
||||
if defaults:
|
||||
builds = defaults
|
||||
builds = random_party_builds(builds)
|
||||
by_role: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for build in builds:
|
||||
by_role[build["role"]].append(build)
|
||||
@@ -178,6 +176,21 @@ def random_party(builds: Sequence[dict[str, Any]], seed: int) -> list[dict[str,
|
||||
return [generator.choice(by_role["tank"]), generator.choice(by_role["healer"]), *damage]
|
||||
|
||||
|
||||
def random_party_builds(builds: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
defaults = [build for build in builds if build.get("defaultTalentBuild")]
|
||||
return defaults or list(builds)
|
||||
|
||||
|
||||
def random_party_capacity(builds: Sequence[dict[str, Any]]) -> int:
|
||||
"""Return the number of unique 1/1/3 compositions in the random pool."""
|
||||
by_role: dict[str, int] = defaultdict(int)
|
||||
for build in random_party_builds(builds):
|
||||
by_role[build["role"]] += 1
|
||||
if not by_role["tank"] or not by_role["healer"] or by_role["damage"] < 3:
|
||||
return 0
|
||||
return by_role["tank"] * by_role["healer"] * math.comb(by_role["damage"], 3)
|
||||
|
||||
|
||||
def readiness_eligible_builds(
|
||||
builds: Sequence[dict[str, Any]],
|
||||
policy: dict[str, Any] | None,
|
||||
@@ -209,14 +222,28 @@ def automatic_parties(
|
||||
configurations: list[dict[str, Any]] = []
|
||||
for source in sources:
|
||||
source_builds = [build for build in builds if build["source"] == source]
|
||||
capacity = random_party_capacity(source_builds)
|
||||
if capacity < 1:
|
||||
raise SystemExit(
|
||||
f"The {source} eligible pool does not expose enough tank, healer, and damage builds "
|
||||
"for a 1/1/3 party."
|
||||
)
|
||||
target_configurations = min(configurations_per_source, capacity)
|
||||
if target_configurations < configurations_per_source:
|
||||
print(
|
||||
f"[balance-lab] {source} eligible pool has {capacity} unique party "
|
||||
f"configuration{'s' if capacity != 1 else ''}; running each once instead of "
|
||||
f"duplicating {configurations_per_source - capacity} identical parties.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
seen: set[tuple[str, str, tuple[str, ...]]] = set()
|
||||
attempts = 0
|
||||
while len(seen) < configurations_per_source:
|
||||
while len(seen) < target_configurations:
|
||||
attempts += 1
|
||||
if attempts > max(1_000, configurations_per_source * 100):
|
||||
if attempts > max(1_000, target_configurations * 100):
|
||||
raise SystemExit(
|
||||
f"Could not generate {configurations_per_source} unique {source} parties. "
|
||||
"Request fewer party configurations."
|
||||
f"Could not generate {target_configurations} unique {source} parties from a "
|
||||
f"calculated capacity of {capacity}."
|
||||
)
|
||||
party = random_party(source_builds, seed + offsets[source] + attempts - 1)
|
||||
signature = (party[0]["id"], party[1]["id"], tuple(sorted(member["id"] for member in party[2:])))
|
||||
|
||||
@@ -71,8 +71,11 @@ const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-balance-ui
|
||||
assert(Number(await page.getByLabel('Coverage parties').inputValue()) > 1, 'all-spec coverage should derive multiple parties');
|
||||
await page.getByLabel('Class/spec selection').selectOption('specific');
|
||||
await page.getByLabel('Search Balance Lab class specs').fill('Paladin Holy');
|
||||
await page.getByRole('checkbox', { name: /Paladin · Holy/ }).check();
|
||||
await page.getByRole('checkbox', { name: /Paladin · Holy.*Holy Playerbots PvE/ }).check();
|
||||
assert.equal(await page.getByLabel('Coverage parties').inputValue(), '1');
|
||||
await page.getByLabel('Class/spec selection').selectOption('random');
|
||||
await page.getByLabel('Parties per source').fill('10');
|
||||
await page.getByText(/eligible pools provide 1 unique party configuration/i).waitFor();
|
||||
await page.getByLabel('Runs per party/boss').fill('1');
|
||||
await page.getByLabel('Fight limit (seconds)').fill('5');
|
||||
await page.getByLabel('Bosses').selectOption('specific');
|
||||
@@ -86,7 +89,7 @@ const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-balance-ui
|
||||
await page.getByRole('button', { name: 'Run on server' }).click();
|
||||
await page.locator('.balance-lab-job-list em.is-complete').waitFor({ timeout: 240_000 });
|
||||
await page.getByRole('button', { name: 'Open full report' }).waitFor();
|
||||
assert.equal(await page.locator('.balance-lab-artifacts > button').count(), 12);
|
||||
assert((await page.locator('.balance-lab-artifacts > button').count()) >= 12);
|
||||
await capture('04-server-job-complete');
|
||||
|
||||
await page.getByRole('button', { name: 'View graphs and filterable data' }).click();
|
||||
@@ -100,7 +103,7 @@ const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-balance-ui
|
||||
assert.match(await page.locator('.balance-results-table-panel tbody').innerText(), /Eck the Ferocious/);
|
||||
await page.getByRole('button', { name: 'Class / spec', exact: true }).click();
|
||||
const allClassRows = await page.locator('.balance-results-table-panel tbody tr').count();
|
||||
assert(allClassRows >= 5, 'the complete five-member party should be present in the class/spec table');
|
||||
assert.equal(allClassRows, 4, 'the four NPC teammates should be present in the NPC-only class/spec table');
|
||||
await page.getByLabel('Sort class specs').selectOption('totalDamageTaken');
|
||||
assert.equal(await page.getByLabel('Sort class specs').inputValue(), 'totalDamageTaken');
|
||||
await page.getByLabel('Class role').selectOption('damage');
|
||||
@@ -148,6 +151,17 @@ const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-balance-ui
|
||||
await page.getByText('Every class/spec ability').scrollIntoViewIfNeeded();
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth), true);
|
||||
await capture('07-mobile-abilities');
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||
await page.getByLabel('Level coverage').selectOption('brackets');
|
||||
await page.getByLabel('Bracket spacing').selectOption('10');
|
||||
assert.equal(await page.locator('.balance-lab-estimate span').filter({ hasText: 'Level jobs' }).locator('b').innerText(), '9');
|
||||
await page.getByRole('button', { name: 'Queue 9 level jobs' }).click();
|
||||
await page.waitForFunction(() => (
|
||||
document.querySelectorAll('.balance-lab-job-list em.is-complete').length >= 10
|
||||
), null, { timeout: 240_000 });
|
||||
assert.equal(await page.locator('.balance-lab-job-list em.is-failed').count(), 0);
|
||||
await capture('08-nine-bracket-jobs-complete');
|
||||
assert.deepEqual(pageErrors, []);
|
||||
console.log('Balance Lab GM playtest passed');
|
||||
} finally {
|
||||
|
||||
@@ -6,6 +6,7 @@ from scripts.balance_lab import (
|
||||
automatic_parties,
|
||||
coverage_parties,
|
||||
random_party,
|
||||
random_party_capacity,
|
||||
readiness_eligible_builds,
|
||||
resolve_build,
|
||||
runtime_session_command,
|
||||
@@ -89,6 +90,19 @@ class BalanceLabControllerTests(unittest.TestCase):
|
||||
"tank", "healer", "damage", "damage", "damage",
|
||||
])
|
||||
|
||||
def test_automatic_parties_cap_to_the_unique_ready_pool(self) -> None:
|
||||
builds = [
|
||||
{"id": "ready-tank", "source": "wow", "role": "tank", "defaultTalentBuild": True},
|
||||
{"id": "ready-healer", "source": "wow", "role": "healer", "defaultTalentBuild": True},
|
||||
*({"id": f"ready-damage-{index}", "source": "wow", "role": "damage", "defaultTalentBuild": True}
|
||||
for index in range(3)),
|
||||
{"id": "testing-tank-alt", "source": "wow", "role": "tank", "defaultTalentBuild": False},
|
||||
]
|
||||
self.assertEqual(random_party_capacity(builds), 1)
|
||||
configurations = automatic_parties(builds, ("wow",), 10, 29)
|
||||
self.assertEqual(len(configurations), 1)
|
||||
self.assertEqual(len({member["id"] for member in configurations[0]["party"]}), 5)
|
||||
|
||||
def test_coverage_parties_include_every_build_with_minimum_unique_parties(self) -> None:
|
||||
builds = [
|
||||
{"id": "tank-a", "source": "wow", "role": "tank"},
|
||||
|
||||
Reference in new issue
Block a user