Guard lethal stuns against a replacement deployment; keep bard auras running through stun; require an active Aegir bond for Clementia's strategy; allow Compass SP to bypass recovery locks; and refresh Hidden Core gun mitigation against current gun/spring companions. Set multiplayer strategy picks to 50s and describe renderer fallback without diagnosing unsupported hardware. Golden moves (full 285-scenario regeneration): - roster-008: inactive Aegir no longer gains 18 strategy layers. - boss-boss_4-solo: bard regeneration continues through stun. - hidden-boss_9-pair: mitigation covers first hits/current companions. - standin-07: hammer stun cannot follow an immediate revival deployment. No ending-reason changes. Sources and report-by-report findings: DESIGN 28.25.
88 lines
5.9 KiB
JavaScript
88 lines
5.9 KiB
JavaScript
// Community reports of 2026-10-10: actual attacks / skills, including death and redeployment in the same tick.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
|
|
|
const bond = (layers = 0) => ({ count: 3, active: true, tier: 1, layers });
|
|
const dummy = enemyRec({ key: 'dummy', hp: 1e9, atk: 0, speed: 0, mass: 0 });
|
|
const wall = (id, opts = {}) => chessRec({ id, skill: null, stats: { maxHp: 1e7, atk: 0, def: 0, blockCnt: 3 }, ...opts });
|
|
const finish = (h) => { checkInvariants(h.b); assert.deepEqual(h.b.errors, []); };
|
|
const near = (actual, expected) => assert.ok(Math.abs(actual - expected) < 1e-6, `${actual} != ${expected}`);
|
|
|
|
for (const key of ['enemy_1045_hammer', 'enemy_1045_hammer_2', 'enemy_1160_hvyslr', 'enemy_1160_hvyslr_2']) {
|
|
test(`${key}: a lethal stunning strike cannot stun the deployment created by 不屈`, () => {
|
|
const h = makeBattle({ autoFinish: false, timeLimit: 100, defs: { chess: { wall: wall('wall') } },
|
|
units: [{ chessId: 'wall', row: 10, col: 5 }], bonds: { indomShip: bond(300) } });
|
|
const nth = key.includes('hvyslr') ? 4 : 3;
|
|
h.step(); const u = h.unit('wall'); const e = h.spawn(key, { pos: [10, 5], mods: { speedMul: 0 } });
|
|
assert.ok(h.runUntil(() => e.stats.attacks >= nth - 1, 40));
|
|
const seq = u.deploySeq; u.hp = 1;
|
|
assert.ok(h.runUntil(() => e.stats.attacks >= nth, 20));
|
|
assert.ok(u.alive && u.deploySeq > seq, 'the lethal stunning strike immediately redeployed the operator');
|
|
assert.ok(!u.s.flags.stun, 'the old attack cannot put stun on that new deployment');
|
|
// The next stunning strike that does not kill must still stun normally.
|
|
assert.ok(h.runUntil(() => e.stats.attacks >= nth * 2, 40));
|
|
assert.ok(u.s.flags.stun); finish(h);
|
|
});
|
|
}
|
|
|
|
for (const skadi of [false, true]) {
|
|
test(`${skadi ? '浊心斯卡蒂 custom' : 'default'} bard keeps its regeneration while stunned, stops after leaving`, () => {
|
|
const bard = chessRec({ id: 'bard', profession: 'SUPPORT', subProfessionId: 'bard', attackKind: 'none', dmgType: 'heal',
|
|
skill: null, rangeGrid: [[0, 0], [0, 1]], stats: { atk: 1000 } });
|
|
const h = makeBattle({ autoFinish: false, defs: { chess: { bard, wall: wall('wall') } },
|
|
units: [{ chessId: skadi ? 'chess_char_6_04_a' : 'bard', row: 10, col: 4, skillIndex: 1 }, { chessId: 'wall', row: 10, col: 5 }] });
|
|
h.step(); const u = h.b.allyUnits[0], a = h.unit('wall'); a.hp = 100;
|
|
if (skadi) { u.skill.gainSp(999, 'init'); assert.ok(u.skill.activate('test')); }
|
|
h.run(1); const regen = a.s.hpRegen; assert.ok(regen > 0);
|
|
h.b.applyStatus(u, 'stun', { duration: 10 }); h.run(2);
|
|
near(a.s.hpRegen, regen); const hp = a.hp; h.run(1); assert.ok(a.hp > hp);
|
|
h.b.kill(u, null); h.run(1); near(a.s.hpRegen, 0); finish(h);
|
|
});
|
|
}
|
|
|
|
test('黄沙罗盘 + 浓茶 bypass both the active skill lock and noSp, including the caster; ordinary SP still obeys them', () => {
|
|
const op = (id, bonds) => chessRec({ id, bonds, skill: { spCost: 100, initSp: 0, duration: 20 } });
|
|
const h = makeBattle({ autoFinish: false, defs: { chess: { a: op('a', ['sargonShip']), b: op('b', ['sargonShip']), c: op('c', []) } },
|
|
units: [{ chessId: 'a', row: 10, col: 4, items: ['chess_item_6_07_e_a', 'chess_item_2_04_e_a'] },
|
|
{ chessId: 'b', row: 11, col: 4 }, { chessId: 'c', row: 12, col: 4 }] });
|
|
h.step(); const a = h.unit('a'), b = h.unit('b'), c = h.unit('c');
|
|
for (const u of [a, b, c]) { u.skill.gainSp(100, 'init'); h.b.addBuff(u, { key: 'test:lock', flags: { noSp: true } }); }
|
|
assert.ok(b.skill.activate('test')); assert.ok(c.skill.activate('test')); assert.ok(a.skill.activate('test'));
|
|
near(a.skill.sp, 3); near(b.skill.sp, 3); near(c.skill.sp, 0);
|
|
near(b.skill.gainSp(5, 'item'), 0); near(b.skill.sp, 3);
|
|
a.skill.end('test'); near(a.skill.sp, 33); finish(h);
|
|
});
|
|
|
|
for (const elite of [false, true]) {
|
|
test(`蕾缪安 S3 real ammo consumption gains active bonds with three operators in the row (${elite ? 'elite' : 'normal'})`, () => {
|
|
const id = `chess_char_6_01_${elite ? 'b' : 'a'}`;
|
|
const h = makeBattle({ autoFinish: false, timeLimit: 120, hooks: ['ammoUsed'],
|
|
defs: { chess: { a: wall('a'), b: wall('b') }, enemies: { dummy } },
|
|
bonds: { lateranoShip: bond(), preciShip: bond() },
|
|
units: [{ chessId: id, row: 10, col: 4 }, { chessId: 'a', row: 10, col: 2 }, { chessId: 'b', row: 10, col: 3 }] });
|
|
h.step(); const u = h.unit(id); h.spawn('dummy', { pos: [10, 6] });
|
|
for (let i = 0; i < 2; i++) { u.skill.gainSp(999, 'init'); assert.ok(u.skill.activate('test')); assert.ok(h.runUntil(() => !u.skill.active, 30)); }
|
|
const spent = h.hooksOf('ammoUsed').filter((c) => c.unit === u).length;
|
|
assert.ok(spent >= 10); const gains = h.result().perPlayer.p1.layerGains;
|
|
for (const b of ['lateranoShip', 'preciShip']) assert.equal(gains[b], Math.floor(spent / 10) * (elite ? 4 : 2), b);
|
|
finish(h);
|
|
});
|
|
}
|
|
|
|
for (const id of ['chess_char_4_23_a', 'chess_char_5_18_a']) {
|
|
test(`${id} 百炼嘉维尔 S2 damages a final boss but cannot pull it`, () => {
|
|
const sharedBoss = { hp: 1e9, maxHp: 1e9, damage(pid, n) { this.hp -= n; } };
|
|
const h = makeBattle({ kind: 'boss', sharedBoss, autoFinish: false, hooks: ['damaged'], captureNoisy: true,
|
|
units: [{ chessId: id, row: 10, col: 3, skillIndex: 1 }] });
|
|
h.step(); const u = h.unit(id);
|
|
const e = h.spawn('enemy_9017_achunt', { pos: [3, 5], tag: 'boss', mods: { speedMul: 0 } });
|
|
e.profile.noAttack = true;
|
|
u.skill.gainSp(999, 'init'); assert.ok(u.skill.activate('test')); h.run(3);
|
|
assert.ok(h.hooksOf('damaged').some((c) => c.source === u && c.target === e), 'S2 actually hits the leader');
|
|
near(e.x, 5); near(e.y, 3);
|
|
// An excessive force isolates the leader exclusion from its ordinary high weight.
|
|
near(h.b.pullToFront(e, u, 99), 0); near(e.x, 5); near(e.y, 3); finish(h);
|
|
});
|
|
}
|