Files
ark-stronghold-protocol/test/content/new-modules-023.test.js
T
sganggs 2e122873f0 0.2.3: add Clementia and Shu/Ulpianus modules (#437)
Regenerate roster-019, roster-038 and diy-059 through diy-140 for added and regrouped loadouts; other golden families unchanged.
2026-10-09 17:10:30 +08:00

115 lines
5.8 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.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { KITS } from '../../server/sim/content/kits/index.js';
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
const GUAY = 'uniequip_003_shu', CRUY = 'uniequip_003_ulpia';
const approx = (a, b) => assert.ok(Math.abs(a - b) < 1e-6, `${a} != ${b}`);
const target = enemyRec({ key: 'dummy', hp: 1e8, speed: 0, def: 0, atk: 0 });
const war = chessRec({ id: 'war', profession: 'WARRIOR', skill: null, stats: { atk: 100, blockCnt: 0 } });
const med = chessRec({ id: 'med', profession: 'MEDIC', skill: null, stats: { atk: 100, blockCnt: 0 } });
const helpers = [2, 3, 4].map((uid) => ({ uid, chessId: 'war', row: 10, col: uid }));
function shu(tier, others = []) {
const h = makeBattle({ defs: { enemies: { dummy: target }, chess: { war, med } }, autoFinish: false,
units: [{ uid: 1, diy: { slot: tier, charId: 'char_2025_shu', skillIndex: 2, uniEquipId: GUAY }, elite: true, row: 11, col: 5 }, ...others] });
h.step(); return { h, u: h.unit(1) };
}
test('黍 GUA-Y stage 1/3 reduces incoming damage by 15%, independently of HP, after deployment', () => {
for (const tier of [5, 6]) {
const { h, u } = shu(tier);
approx(u.s.dmgTakenMul, 0.85);
const e = h.spawn('dummy', { pos: [10, 9] });
const hp = u.hp;
h.b.dealDamage(e, u, { amount: 100, type: 'true' });
approx(hp - u.hp, 85);
const hp2 = u.hp;
h.b.dealDamage(e, u, { amount: 100, type: 'elemental' });
approx(hp2 - u.hp, 100);
checkInvariants(h.b); assert.equal(h.b.errors.length, 0);
}
});
test('黍 GUA-Y stage 3 requires two conditions, buffs each operator once, and drops the bonus when a condition ends', () => {
for (const tier of [5, 6]) {
const { h, u } = shu(tier, [...helpers, { uid: 5, chessId: 'med', row: 9, col: 3 }]);
h.run(0.3);
const a = h.unit(2);
approx(a.s.atk / a.base.atk, tier === 6 ? 1.05 : 1);
assert.equal(a.s.aspd, tier === 6 ? 116 : 112);
h.b.retreat(h.unit(5), { permanent: true });
h.run(0.7);
approx(a.s.atk / a.base.atk, 1);
assert.equal(u.s.aspd, tier === 6 ? 116 : 112);
checkInvariants(h.b); assert.equal(h.b.errors.length, 0);
}
});
function ulpia({ moduleId = CRUY, potential = 6, stage3 = false } = {}) {
// The mode's tier-5 elite only exposes module stage 1. Exercise its stage-3 talent separately using the
// official CRU-Y blackboard (battle_equip_table, 2026-10-09); do not change the production slot's module level.
const kits = stage3 ? { chess_char_5_05_a: (bb, raw, def) => {
const rec = structuredClone(raw);
rec.talents.find((t) => t.index === 1).bb = {
max_stack_cnt: potential === 6 ? 10 : 9, max_hp: 150, atk: 38,
'ulpia_t_1[abyssal].max_hp': 90, 'ulpia_t_1[abyssal].atk': 22.799999,
'ulpia_t_1[abyssal].max_stack_cnt': potential === 6 ? 10 : 9,
block_cnt: 1, add_block_cnt_stack_cnt: 9,
};
return KITS.chess_char_5_05_a(bb, rec, def);
} } : undefined;
const h = makeBattle({ autoFinish: false, kits, defs: { enemies: { dummy: target, victim: enemyRec({ key: 'victim', hp: 1, atk: 0, speed: 0 }) } },
units: [{ uid: 1, chessId: 'chess_char_5_05_b', row: 10, col: 5, moduleId, skillIndex: 0, potential },
{ uid: 2, chessId: 'chess_char_3_05_a', row: 11, col: 5 },
{ uid: 3, chessId: 'chess_char_1_01_a', row: 9, col: 5 }] });
h.step(); return { h, u: h.unit(1), hunter: h.unit(2), other: h.unit(3) };
}
test('乌尔比安 CRU-Y scales damage against enemies blocked by any ally, including skill damage', () => {
const { h, u, hunter } = ulpia();
const e = h.spawn('dummy', { pos: [10, 7] });
const amount = () => h.b.dealDamage(u, e, { amount: 100, type: 'phys', isSkill: true });
approx(amount(), 100);
e.blockedBy = hunter; hunter.blocking.push(e);
approx(amount(), 110);
e.blockedBy = null; hunter.blocking.length = 0;
checkInvariants(h.b); assert.equal(h.b.errors.length, 0);
});
test('乌尔比安 CRU-Y hunters share kill stacks; block +1 at nine, potential cap ten, other operators do not trigger it', () => {
for (const potential of [1, 6]) {
const { h, u, hunter, other } = ulpia({ potential, stage3: true });
const kill = (killer) => h.b.dealDamage(killer, h.spawn('victim', { pos: [10, 8] }), { amount: 100, type: 'true' });
kill(other); assert.equal(u.findBuff('ulpia:blood'), null);
const block = u.s.blockCnt;
for (let n = 1; n <= 12; n++) {
kill(hunter);
assert.equal(u.findBuff('ulpia:blood')?.stacks, Math.min(n, potential === 6 ? 10 : 9));
assert.equal(u.s.blockCnt, block + (n >= 9 ? 1 : 0));
}
assert.ok(hunter.findBuff('ulpia:bloodShare'));
checkInvariants(h.b); assert.equal(h.b.errors.length, 0);
}
});
test('乌尔比安 without the new module still only gains stacks from his own kills', () => {
const { h, u, hunter } = ulpia({ moduleId: 'none' });
h.b.dealDamage(hunter, h.spawn('victim', { pos: [10, 8] }), { amount: 100, type: 'true' });
assert.equal(u.findBuff('ulpia:blood'), null);
h.b.dealDamage(u, h.spawn('victim', { pos: [10, 8] }), { amount: 100, type: 'true' });
assert.equal(u.findBuff('ulpia:blood').stacks, 1);
checkInvariants(h.b); assert.equal(h.b.errors.length, 0);
});
test('乌尔比安 CRU-Y counts a withdrawn hunter’s delayed credited kill only while Ulpianus is deployed', () => {
const { h, u, hunter } = ulpia({ stage3: true });
h.b.retreat(hunter, { permanent: true });
h.b.dealDamage(hunter, h.spawn('victim', { pos: [10, 8] }), { amount: 100, type: 'true' });
assert.equal(u.findBuff('ulpia:blood')?.stacks, 1);
h.b.retreat(u, { permanent: true });
h.b.dealDamage(hunter, h.spawn('victim', { pos: [10, 8] }), { amount: 100, type: 'true' });
assert.ok((u.findBuff('ulpia:blood')?.stacks ?? 0) <= 1);
checkInvariants(h.b); assert.deepEqual(h.b.errors, []);
});