Release v0.1.5 2026-07-12
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { freshParty } from "../data";
|
||||
import { createBaseMotion } from "./shared";
|
||||
import { advancePooledBossMechanics, POOLED_MECHANIC_TIMING, SOUL_SIPHON } from "./mechanicPool";
|
||||
import type { BossMechanicContext, BossMechanicResult } from "./types";
|
||||
import { advanceMechanicLoadout, BOSS_MECHANIC_POOL, bossAnimationCue, SOUL_SIPHON } from "./mechanicPool";
|
||||
import type { BossMechanicContext } from "./types";
|
||||
import type { BossState, PoolTelegraph, WorldPosition } from "../types";
|
||||
|
||||
const POSITIONS: BossMechanicContext["partyPositions"] = {
|
||||
@@ -20,9 +20,6 @@ function state(): BossState {
|
||||
maxHp: 500,
|
||||
hp: 500,
|
||||
nextMeleeAt: Number.POSITIVE_INFINITY,
|
||||
nextNovaAt: Number.POSITIVE_INFINITY,
|
||||
nextBrandAt: Number.POSITIVE_INFINITY,
|
||||
brandCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,10 +35,6 @@ function context(time: number, positions = POSITIONS): BossMechanicContext {
|
||||
};
|
||||
}
|
||||
|
||||
function result(contextValue: BossMechanicContext): BossMechanicResult {
|
||||
return { boss: contextValue.boss, motion: contextValue.motion, party: contextValue.party, events: [] };
|
||||
}
|
||||
|
||||
function memoryTelegraph(): PoolTelegraph {
|
||||
return {
|
||||
id: "test-memory",
|
||||
@@ -79,10 +72,10 @@ function advanceMemory(
|
||||
source.party = party;
|
||||
source.motion = {
|
||||
...source.motion,
|
||||
nextPoolMechanicAt: Number.POSITIVE_INFINITY,
|
||||
activeMechanicId: "memory-sequence",
|
||||
poolTelegraphs: [telegraph],
|
||||
};
|
||||
return advancePooledBossMechanics(source, result(source));
|
||||
return advanceMechanicLoadout(source, ["memory-sequence", "bull-charge"]);
|
||||
}
|
||||
|
||||
function soulSiphonTelegraph(): PoolTelegraph {
|
||||
@@ -121,21 +114,31 @@ function advanceSoulSiphon(
|
||||
source.party = party;
|
||||
source.motion = {
|
||||
...source.motion,
|
||||
nextPoolMechanicAt: Number.POSITIVE_INFINITY,
|
||||
activeMechanicId: "soul-siphon",
|
||||
poolTelegraphs: [telegraph],
|
||||
};
|
||||
return advancePooledBossMechanics(source, result(source));
|
||||
return advanceMechanicLoadout(source, ["soul-siphon", "bull-charge"]);
|
||||
}
|
||||
|
||||
describe("shared boss mechanic pool", () => {
|
||||
it("schedules a telegraphed pool mechanic and keeps its warning state independent of boss mode", () => {
|
||||
const source = context(POOLED_MECHANIC_TIMING.firstAt);
|
||||
const started = advancePooledBossMechanics(source, result(source));
|
||||
it.each(BOSS_MECHANIC_POOL.filter(({ id }) => id !== "basic-melee"))("starts $name directly from its canonical ID", ({ id }) => {
|
||||
const source = context(0);
|
||||
source.motion.nextMechanicAt = 0;
|
||||
const started = advanceMechanicLoadout(source, [id, id]);
|
||||
|
||||
expect(started.motion.mode).toBe("holding");
|
||||
expect(started.motion.activeMechanicId).toBe(id);
|
||||
expect(["idle", "move", "attack", "special"]).toContain(bossAnimationCue(started.motion));
|
||||
});
|
||||
|
||||
it("schedules the exact mechanic ID selected by a boss loadout", () => {
|
||||
const source = context(0);
|
||||
source.motion.nextMechanicAt = 0;
|
||||
const started = advanceMechanicLoadout(source, ["meteor-spread", "bull-charge"]);
|
||||
|
||||
expect(started.motion.activeMechanicId).toBe("meteor-spread");
|
||||
expect(started.motion.poolTelegraphs).not.toHaveLength(0);
|
||||
expect(started.events[0].message).toContain(":");
|
||||
expect(started.motion.nextPoolMechanicAt).toBe(Number.POSITIVE_INFINITY);
|
||||
expect(bossAnimationCue(started.motion)).toBe("attack");
|
||||
});
|
||||
|
||||
it("splits soak damage across allies inside its indicator", () => {
|
||||
@@ -156,14 +159,14 @@ describe("shared boss mechanic pool", () => {
|
||||
resolved: false,
|
||||
hitIds: [],
|
||||
};
|
||||
const motion = { ...source.motion, nextPoolMechanicAt: Number.POSITIVE_INFINITY, poolTelegraphs: [soak] };
|
||||
const motion = { ...source.motion, activeMechanicId: "aetheric-soak" as const, poolTelegraphs: [soak] };
|
||||
const positions = structuredClone(POSITIONS);
|
||||
positions.aelia = [0.2, 0];
|
||||
positions.brann = [0, 0];
|
||||
positions.nia = [-0.2, 0];
|
||||
positions.vale = [0, -4];
|
||||
const atImpact = { ...source, motion, partyPositions: positions, time: activatesAt };
|
||||
const resolved = advancePooledBossMechanics(atImpact, result(atImpact));
|
||||
const resolved = advanceMechanicLoadout(atImpact, ["aetheric-soak", "bull-charge"]);
|
||||
|
||||
expect(resolved.party.find((member) => member.id === "aelia")!.hp).toBe(source.party[0].hp - 30);
|
||||
expect(resolved.party.find((member) => member.id === "brann")!.hp).toBe(source.party[1].hp - 30);
|
||||
@@ -192,11 +195,11 @@ describe("shared boss mechanic pool", () => {
|
||||
positions.brann = [3, 0];
|
||||
const atImpact = {
|
||||
...source,
|
||||
motion: { ...source.motion, nextPoolMechanicAt: Number.POSITIVE_INFINITY, poolTelegraphs: [donut] },
|
||||
motion: { ...source.motion, activeMechanicId: "hollow-collapse" as const, poolTelegraphs: [donut] },
|
||||
partyPositions: positions,
|
||||
time: activatesAt,
|
||||
};
|
||||
const resolved = advancePooledBossMechanics(atImpact, result(atImpact));
|
||||
const resolved = advanceMechanicLoadout(atImpact, ["hollow-collapse", "bull-charge"]);
|
||||
|
||||
expect(resolved.party.find((member) => member.id === "aelia")!.hp).toBe(source.party[0].hp);
|
||||
expect(resolved.party.find((member) => member.id === "brann")!.hp).toBe(source.party[1].hp - 25);
|
||||
|
||||
Reference in New Issue
Block a user