Regenerate roster-019, roster-038 and diy-059 through diy-140 for added and regrouped loadouts; other golden families unchanged.
115 lines
5.8 KiB
JavaScript
115 lines
5.8 KiB
JavaScript
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, []);
|
||
});
|