Release v0.1.3 2026-07-11
This commit is contained in:
+81
-4
@@ -4,6 +4,8 @@ import { distance, pointToSegmentDistance } from "./geometry";
|
||||
import { barrierProtects, useGameStore } from "./store";
|
||||
import { createClassInventory, HEALER_CLASSES } from "./healers";
|
||||
import { dropVexaVenomPool, VEXA_VENOM } from "./bosses/vexa";
|
||||
import { isInsideArena } from "./arena";
|
||||
import { BOSS_DEFINITIONS } from "./bossCatalog";
|
||||
|
||||
describe("Disc Priest combat simulation", () => {
|
||||
beforeEach(() => {
|
||||
@@ -148,7 +150,7 @@ describe("Disc Priest combat simulation", () => {
|
||||
expect(barrierProtects(barrier.center, barrier, 8)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps ranged allies stable while Vale closes to melee range", () => {
|
||||
it("keeps ranged allies stable while Vale holds behind the boss", () => {
|
||||
const start = structuredClone(useGameStore.getState().partyPositions);
|
||||
const bossPosition = useGameStore.getState().bossMotion.position;
|
||||
useGameStore.getState().tick(1);
|
||||
@@ -157,7 +159,8 @@ describe("Disc Priest combat simulation", () => {
|
||||
expect(moved.brann).toEqual(start.brann);
|
||||
expect(moved.nia).toEqual(start.nia);
|
||||
expect(moved.orin).toEqual(start.orin);
|
||||
expect(distance(moved.vale, bossPosition)).toBeLessThan(distance(start.vale, bossPosition));
|
||||
expect(moved.brann[1]).toBeGreaterThan(bossPosition[1]);
|
||||
expect(moved.vale[1]).toBeLessThan(bossPosition[1]);
|
||||
});
|
||||
|
||||
it("freezes authoritative simulation while paused", () => {
|
||||
@@ -255,7 +258,7 @@ describe("Disc Priest combat simulation", () => {
|
||||
for (const memberId of exposedAtWarning) expect(hitIds).not.toContain(memberId);
|
||||
});
|
||||
|
||||
it("marks a stack target after three charges and splits 300 pounce damage", () => {
|
||||
it("marks a stack target after three charges and splits 200 pounce damage", () => {
|
||||
useGameStore.setState((state) => ({
|
||||
boss: { ...state.boss, nextMeleeAt: 999, nextNovaAt: 999, nextBrandAt: 999 },
|
||||
playerPosition: [0, 0],
|
||||
@@ -291,7 +294,7 @@ describe("Disc Priest combat simulation", () => {
|
||||
const impacted = useGameStore.getState();
|
||||
expect(impacted.bossMotion.mode).toBe("returning");
|
||||
for (const member of impacted.party) {
|
||||
expect(member.hp).toBeCloseTo(startingHp[member.id] - 42, 3);
|
||||
expect(member.hp).toBeCloseTo(startingHp[member.id] - 28, 3);
|
||||
}
|
||||
expect(impacted.partyCombat.tankAura.expiresAt).toBeGreaterThan(impacted.time);
|
||||
});
|
||||
@@ -412,6 +415,80 @@ describe("PVE dual-boss encounter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Roguelike rounds", () => {
|
||||
beforeEach(() => {
|
||||
useGameStore.getState().configureHealer(
|
||||
"priest",
|
||||
"Aelia",
|
||||
createClassInventory("priest"),
|
||||
["bulldrome", "vexa"],
|
||||
"roguelike",
|
||||
);
|
||||
useGameStore.getState().startEncounter();
|
||||
});
|
||||
|
||||
it("blocks progression for a buff, then starts round two with new bosses at 110% base HP", () => {
|
||||
const roundOne = useGameStore.getState();
|
||||
const previousBossIds = [roundOne.boss.id, roundOne.additionalBosses[0].boss.id];
|
||||
useGameStore.setState((state) => ({
|
||||
boss: { ...state.boss, hp: 0 },
|
||||
additionalBosses: state.additionalBosses.map((entry) => ({ ...entry, boss: { ...entry.boss, hp: 0 } })),
|
||||
}));
|
||||
|
||||
useGameStore.getState().tick(0.05);
|
||||
expect(useGameStore.getState().phase).toBe("intermission");
|
||||
const intermissionTime = useGameStore.getState().time;
|
||||
useGameStore.getState().tick(10);
|
||||
expect(useGameStore.getState().phase).toBe("intermission");
|
||||
expect(useGameStore.getState().round).toBe(1);
|
||||
expect(useGameStore.getState().time).toBe(intermissionTime);
|
||||
expect(useGameStore.getState().chooseRunBuff("vital-bloom")).toBe(true);
|
||||
|
||||
const roundTwo = useGameStore.getState();
|
||||
const nextBossIds = [roundTwo.boss.id, roundTwo.additionalBosses[0].boss.id];
|
||||
expect(roundTwo.phase).toBe("combat");
|
||||
expect(roundTwo.round).toBe(2);
|
||||
expect(roundTwo.runBuffs).toEqual(["vital-bloom"]);
|
||||
expect(nextBossIds.every((bossId) => !previousBossIds.includes(bossId))).toBe(true);
|
||||
expect(roundTwo.boss.maxHp).toBe(Math.round(BOSS_DEFINITIONS[roundTwo.boss.id].maxHp * 1.1));
|
||||
expect(roundTwo.additionalBosses[0].boss.maxHp).toBe(Math.round(BOSS_DEFINITIONS[roundTwo.additionalBosses[0].boss.id].maxHp * 1.1));
|
||||
expect(roundTwo.party[0].maxHp).toBe(112);
|
||||
});
|
||||
|
||||
it("rejects buff claims outside intermission", () => {
|
||||
expect(useGameStore.getState().chooseRunBuff("deep-wells")).toBe(false);
|
||||
expect(useGameStore.getState().round).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shared arena boundary", () => {
|
||||
beforeEach(() => {
|
||||
useGameStore.getState().configureHealer("priest", "Aelia", createClassInventory("priest"));
|
||||
useGameStore.getState().startEncounter();
|
||||
});
|
||||
|
||||
it("constrains player, party, and boss positions to the same room", () => {
|
||||
useGameStore.getState().setPlayerPosition([100, 100]);
|
||||
useGameStore.setState((state) => ({
|
||||
boss: { ...state.boss, nextMeleeAt: 999, nextNovaAt: 999, nextBrandAt: 999 },
|
||||
bossMotion: { ...state.bossMotion, position: [100, -100], nextChargeAt: 999 },
|
||||
partyPositions: {
|
||||
...state.partyPositions,
|
||||
brann: [50, 50],
|
||||
nia: [-50, 50],
|
||||
orin: [50, -50],
|
||||
vale: [-50, -50],
|
||||
},
|
||||
}));
|
||||
|
||||
useGameStore.getState().tick(0.1);
|
||||
const state = useGameStore.getState();
|
||||
expect(isInsideArena(state.playerPosition)).toBe(true);
|
||||
for (const position of Object.values(state.partyPositions)) expect(isInsideArena(position)).toBe(true);
|
||||
expect(isInsideArena(state.bossMotion.position)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cindermaw encounter", () => {
|
||||
beforeEach(() => {
|
||||
useGameStore.getState().configureHealer("priest", "Aelia", createClassInventory("priest"), "cindermaw");
|
||||
|
||||
Reference in New Issue
Block a user