49 lines
3.1 KiB
JavaScript
49 lines
3.1 KiB
JavaScript
// Regression: 浊心斯卡蒂 S1 (damage transfer to her) + 坚守 tier 2 (damage cut and shared to the 坚守 members) used to
|
|
// bounce redirected damage between Skadi and the members — every cycle branching once per member — until the hook
|
|
// depth guard: k members ≈ k^16 damage events per hit, which froze a boss field (an automated bot run, seed 2000301
|
|
// solo HARD). Redirected damage is now never redirected again.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
|
import { getDefaultSource } from '../../server/sim/simdata.js';
|
|
import { bondBb } from '../../server/sim/content/bonds/addon/battle.js';
|
|
|
|
const ds = getDefaultSource();
|
|
const SID = 'skchr_skadi2_1';
|
|
const op = (id, bonds) => chessRec({ id, bonds, profession: 'WARRIOR', skill: null, stats: { def: 0, maxHp: 1e6 } });
|
|
const bond = (tier, layers = 0, count = 3) => ({ count, active: tier > 0, tier, layers });
|
|
|
|
for (const id of ['chess_char_6_04_a', 'chess_char_6_04_b']) {
|
|
test(`${id}: Skadi S1 transfer + 坚守 tier 2 share do not bounce redirected damage`, () => {
|
|
const skillIndex = ds.rawChess(id).skills.find((s) => s.skillId === SID).index;
|
|
const share = ds.rawChess(id).skills.find((s) => s.skillId === SID).bb.damage_resistance;
|
|
const cut = bondBb('steadShip').damage_resistance;
|
|
const h = makeBattle({
|
|
seed: 7, autoFinish: false, timeLimit: 400, captureNoisy: true, hooks: ['damaged'],
|
|
defs: { chess: { d_1: op('d_1', ['steadShip']), d_2: op('d_2', ['steadShip']), d_3: op('d_3', ['steadShip']), d_n: op('d_n', []) },
|
|
enemies: { dummy: enemyRec({ key: 'dummy', hp: 1e7, speed: 0 }) } },
|
|
// Skadi and four allies next to her (in her range), three of them 坚守 members
|
|
units: [{ chessId: id, row: 10, col: 4, skillIndex, carryState: { sp: 999 } },
|
|
{ chessId: 'd_1', row: 10, col: 5 }, { chessId: 'd_2', row: 10, col: 3 }, { chessId: 'd_3', row: 11, col: 4 }, { chessId: 'd_n', row: 9, col: 4 }],
|
|
bonds: { steadShip: bond(2, 10) },
|
|
enemies: [{ key: 'dummy', pos: [9, 9] }],
|
|
});
|
|
const u = h.unit(id);
|
|
if (u.skill && !u.skill.active) h.b.startSkill?.(u);
|
|
h.step(2);
|
|
assert.ok(u.skill?.active, 'S1 active');
|
|
const t0 = Date.now();
|
|
const n0 = h.hooksOf('damaged').length;
|
|
h.b.dealDamage(h.enemy('dummy'), h.unit('d_n'), { amount: 1000, type: 'true' });
|
|
const events = h.hooksOf('damaged').length - n0;
|
|
assert.ok(Date.now() - t0 < 500, `one hit resolves at once (${Date.now() - t0} ms)`);
|
|
assert.ok(events <= 8, `a bounded number of damage events (${events})`);
|
|
assert.equal(h.b.errors.filter((e) => /hookDepth/.test(String(e.key ?? e.label ?? JSON.stringify(e)))).length, 0, 'no hook-depth loop');
|
|
// the non-member's hit: Skadi takes her share once; 坚守 shares the cut of the rest once, nothing is redirected twice
|
|
const dn = h.unit('d_n');
|
|
assert.ok(dn.s.maxHp - dn.hp > 0 && dn.s.maxHp - dn.hp < 1000, 'the ally keeps part of the hit');
|
|
assert.ok(share > 0 && cut > 0);
|
|
checkInvariants(h.b);
|
|
});
|
|
}
|