Files
ark-stronghold-protocol/test/sim/feedback5-gravel-double-hit.test.js
T
sganggs 212a073af6 feedback5 CB3: 砾's normal attack hits twice at 50 % 伤害倍率 after DEF, so a 频次 enemy loses 2 per attack (community report of 2026-10-06, item 16.5)
Official: PRTS 砾 特性备注 — every normal attack in her attack animation deals two instances, each 50 % of the final damage
(after DEF / damage reduction, before 重设伤害); the second triggers no 受击回复. Cause: one instance per attack. Now the profile
field hitDmgMul (ai.js resolveHit: DamageInfo mul on each of the `hits` instances, noSp on the later ones) and 砾's kit trait
{ hits: 2, hitDmgMul: 0.5 } — the same total against normal enemies, twice the count against 频次 enemies.

Golden: 7 scenarios with 砾 on the board — roster-004, roster-005, roster-024; bond-indomShip-low / -high and
bond-kazimierzShip-low / -high (she is in 不屈 and 卡西米尔): two damage events per attack; in roster-024 and
bond-kazimierzShip-high the last blow lands on another instance, so kills (and the heals / damage that follow them) shift
between her and her neighbours (roster-024: 砾 19 -> 25 kills, 风丸 13 -> 9). Tests:
test/sim/feedback5-gravel-double-hit.test.js (2, both fail on the parent). Docs: docs/SIM.md (profile flags).
2026-10-07 02:12:37 +08:00

61 lines
3.5 KiB
JavaScript

// test/sim/feedback5-gravel-double-hit.test.js — community report of 2026-10-06 (item 16.5) 「砾的平A是比较特殊的双判,是两次50%
// 伤害倍率(不是攻击倍率),对频次怪的效率应该比正常干员高,但是现在游戏内还是单次判定」: PRTS 砾 特性备注 "在砾的攻击动作下,每次普通攻击造成
// 两段伤害,但是每段最终只会造成50%的伤害(在计算防御/减伤后,在重设伤害前);第二段伤害不会触发目标的受击回复". Up to 0.2.0 every attack was
// one instance. Now two instances of 50 % 伤害倍率 (DamageInfo `mul`, after DEF — the profile's `hitDmgMul`, ai.js resolveHit).
// Run: node --test test/sim/feedback5-gravel-double-hit.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { hitCount } from '../../server/sim/content/enemies/helpers.js';
import { MIN_DAMAGE_RATIO } from '../../server/sim/constants.js';
const near = (a, b, msg) => assert.ok(Math.abs(a - b) <= 1e-6, `${msg}: ${a} vs ${b}`);
/** 砾 on (10,4) facing right, one parked enemy on (10,5); returns her attacks and her damage instances on it. */
function fight(id, enemy, seconds = 5) {
const h = makeBattle({
seed: 2, autoFinish: false, timeLimit: 60, captureNoisy: true, hooks: ['damaged', 'attack'],
defs: { enemies: { enemy_g: enemyRec({ key: 'enemy_g', hp: 1e9, speed: 0, mass: 0, ...enemy }) } },
units: [{ chessId: id, row: 10, col: 4 }],
});
h.step();
const e = h.spawn('enemy_g', { pos: [10, 5] });
return { h, e, run: () => h.run(seconds) };
}
test('砾 (normal, elite): every normal attack is two damage instances of 50 % 伤害倍率 after DEF — the same total, not 50 % 攻击倍率', () => {
for (const id of ['chess_char_2_12_a', 'chess_char_2_12_b']) {
for (const def of [0, 300, 5000]) {
const { h, run } = fight(id, { def });
run();
const u = h.unit(id);
const attacks = h.hooksOf('attack').filter((c) => c.attacker === u).length;
const hits = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack);
assert.ok(attacks >= 3, `${id}: she attacks (${attacks})`);
assert.equal(hits.length, 2 * attacks, `${id} DEF ${def}: two instances per attack`);
const atk = u.s.atk;
const full = Math.max(atk - def, MIN_DAMAGE_RATIO * atk); // one instance of the whole attack, after DEF
for (const c of hits) near(c.amount, 0.5 * full, `${id} DEF ${def}: each instance is 50 % of the mitigated hit`);
// first instance of each attack may give 受击回复, the second may not
const byAttack = new Map();
for (const c of hits) { const l = byAttack.get(c.dmg.attackId) ?? []; l.push(c); byAttack.set(c.dmg.attackId, l); }
for (const l of byAttack.values()) assert.deepEqual(l.map((c) => !!c.dmg.noSp), [false, true], `${id}: the second instance carries no 受击回复`);
checkInvariants(h.b);
}
}
});
test('砾 against a 频次 enemy: two counted hits per attack (one before 0.2.0)', () => {
for (const id of ['chess_char_2_12_a', 'chess_char_2_12_b']) {
const { h, e, run } = fight(id, { hp: 100 });
hitCount(h.b, e, true);
run();
const u = h.unit(id);
const attacks = h.hooksOf('attack').filter((c) => c.attacker === u).length;
assert.ok(attacks >= 3, `${id}: she attacks`);
assert.equal(100 - e.hp, 2 * attacks, `${id}: 2 per attack`);
checkInvariants(h.b);
}
});