Files
ark-stronghold-protocol/test/content/feedback5-regen.test.js
T
sganggs 5c6524e1ad feedback5 WL: 生命回复速度 is not 治疗 — the 吟游者 trait, 调香师, 瑕光 S2, 铃兰 S3 and 锡人 give an hpRegen buff, so 无法被友方治疗 / 禁疗 units get it and 治疗加成 does not scale it (GitHub #96 / #137)
PRTS: 分支特性信息 吟游者 「特性为基于自身攻击力来增加受益者的“生命回复速度”属性」 (魔王, 浊心斯卡蒂); the 备注 of 调香师 熏衣草,
瑕光 S2, 铃兰 S3 and 锡人 S2 「生命恢复的提供方式为增加目标的“生命回复速度”属性…不受治疗加成和禁疗影响」 (铃兰: none in the
first second, refreshed every second); 异常效果 禁疗 「增减生命回复速度…的效果不会被识别为治疗类能力」. These were heals from
the provider, which damage.js refused on 收割者 / 不屈者 / 武者 (noHeal) and under 禁疗 — 隐德来希 gained nothing under 魔王.
This is the separation proposed in PR #135 by @SpiritedAwayCN, written anew (the PR's patch is not used):
- professions.js bardRegen: the trait is an hpRegen buff per bard refreshed every 0.25 s; 魔王's 微尘 ×1.5 goes through
  its `bardRegen` hook (both 魔王 kits); 浊心斯卡蒂's pulse and the 海嗣 token use it too.
- 调香师 (every ally), 瑕光 S2 (the skill range, removed at its end), 铃兰 S3 (from the first full second, removed at its
  end), 锡人 (per 炼金单元, its cached ATK): hpRegen buffs instead of battle.heal.
- damage.js: a `regen` tick takes no healing multiplier and a heal hook cannot change its amount (no 治疗加成 on any
  生命回复速度); 流明's 凡人之愿 and 华法琳's S1 bonus skip a regen tick of their own.
Direct heals are unchanged (still refused / scaled); 魔王 S3's redistribution and 纯烬's HoT are untouched. Other
per-second recoveries without such a note stay heals (引星棘刺, 塞雷娅 S3, 流明 / 纯烬 HoTs; 余 闲云隐市 and 天马之盔 left open).

Golden moved (every one fields 魔王 / 浊心斯卡蒂 / 调香师 / 瑕光 S2 / 铃兰 / 锡人): roster-004, roster-005, roster-006,
roster-011, roster-013, roster-015, roster-016, roster-018, roster-023, roster-025, roster-030, roster-032, roster-035,
roster-037, roster-039, roster-041, roster-042, roster-045, roster-046; bond-deputShip-high, bond-emptyShip-low,
bond-emptyShip-high, bond-indomShip-high, bond-kazimierzShip-low, bond-kazimierzShip-high, bond-sargonShip-low,
bond-sargonShip-high, bond-skillfulShip-high; boss-boss_1-pair, boss-boss_1-solo, boss-boss_2-solo, boss-boss_3-pair,
boss-boss_3-solo, boss-boss_4-solo, boss-boss_5-pair, boss-boss_5-solo, boss-boss_6-solo, boss-boss_7-pair,
boss-boss_7-solo, hidden-boss_8-solo, hidden-boss_9-pair, hidden-boss_9-solo, hidden-boss_10-solo, unite-1;
solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins.
Docs: SIM.md (heal pipeline, bard row), PLAYING.md, kits/README.md (checklist item 11).
2026-10-05 21:13:15 +08:00

93 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.
// test/content/feedback5-regen.test.js — 生命回复速度 is not 治疗 (GitHub #96 / #137). Effects PRTS describes as raising the
// target's 「生命回复速度」 attribute — the 吟游者 trait (分支特性信息 吟游者; 魔王, 浊心斯卡蒂), 调香师's 熏衣草, 瑕光 S2, 铃兰 S3 —
// are hpRegen buffs: 无法被友方治疗 (隐德来希 收割者, 折桠 不屈者) and 禁疗 do not stop them, 治疗加成 does not scale them
// (damage.js heal `regen`); a direct heal is still refused / scaled as before.
// Run: node --test test/content/feedback5-regen.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
const REAPER = 'chess_char_5_06_a', UNYIELD = 'chess_char_2_17_a';
const DEFS = { chess: { t_plain: chessRec({ id: 't_plain', profession: 'TANK', skill: null, stats: { maxHp: 10000, atk: 0, def: 0 } }) } };
/** Provider on (10,4) facing right with 隐德来希 on (10,5) and 折桠 on (11,4) at half HP (both "无法被友方角色治疗"). */
function field(provider, extra = []) {
const h = makeBattle({
defs: DEFS, autoFinish: false, timeLimit: 120,
units: [{ row: 10, col: 4, ...provider }, { chessId: REAPER, row: 10, col: 5 }, { chessId: UNYIELD, row: 11, col: 4 }, ...extra],
});
h.step();
const p = h.unit(provider.chessId), r = h.unit(REAPER), z = h.unit(UNYIELD);
for (const x of [r, z]) x.hp = x.s.maxHp * 0.5;
return { h, p, r, z };
}
/** HP gained over `secs` s against the unit's own 生命回复速度 (at most ~1 HP pending in the regeneration tick). */
function regenOver(h, u, secs) {
const hp0 = u.hp, rate = u.s.hpRegen;
h.run(secs);
return { gained: u.hp - hp0, expected: rate * secs, rate };
}
const PROVIDERS = [
// [label, unit entry, prepare(h, p) → buff key of the provider's 生命回复速度]
['魔王 trait (吟游者)', { chessId: 'chess_char_4_25_a' }, (h, p) => `trait:bard:${p.id}`],
['浊心斯卡蒂 trait (吟游者, S2 loadout: on, trait raised)', { chessId: 'chess_char_6_04_a', skillIndex: 1, carryState: { sp: 999 } }, (h, p) => {
assert.ok(h.runUntil(() => p.skill.active, 5));
return `trait:bard:${p.id}`;
}],
['调香师 熏衣草', { chessId: 'chess_char_2_14_a' }, (h, p) => `flower:lavender:${p.id}`],
['瑕光 S2 慑敌辉光', { chessId: 'chess_char_3_12_a', skillIndex: 1 }, (h, p) => { assert.ok(p.skill.activate('test', { free: true })); return `blemsh:regen:${p.id}`; }],
['铃兰 S3 狐火渺然', { chessId: 'chess_char_5_10_a' }, (h, p) => { assert.ok(p.skill.activate('test', { free: true })); h.run(1.05); return `lisa:fox:${p.id}`; }],
];
test('隐德来希 (收割者) and 折桠 (不屈者) regenerate under 魔王 / 浊心斯卡蒂 / 调香师 / 瑕光 S2 / 铃兰 S3 — none of it is a heal; a direct heal from the same provider is still refused', () => {
for (const [label, entry, prepare] of PROVIDERS) {
const { h, p, r, z } = field(entry);
const key = prepare(h, p);
h.run(1.2); // (the providers' own talent auras settle their ATK first)
for (const x of [r, z]) {
const b = x.findBuff(key);
assert.ok(b && b.mods.hpRegen > 0, `${label}: ${x.defId} carries the 生命回复速度 buff`);
const { gained, expected } = regenOver(h, x, 2);
assert.ok(gained > 0 && Math.abs(gained - expected) <= 1.5, `${label}: ${x.defId} +${gained.toFixed(2)} ≈ ${expected.toFixed(2)}`);
assert.equal(h.b.heal(p, x, 100), 0, `${label}: a direct heal on ${x.defId} is still refused (无法被友方角色治疗)`);
}
assert.equal(h.hooksOf('heal').filter((c) => c.source === p && (c.target === r || c.target === z)).length, 0, `${label}: no heal of the provider reached them`);
checkInvariants(h.b);
assert.equal(h.b.errors.length, 0);
}
});
test('禁疗 does not stop 生命回复速度 (异常效果 禁疗 「增减生命回复速度…的效果不会被识别为治疗类能力」); it still stops heals, the unit\'s own included', () => {
const { h, p } = field({ chessId: 'chess_char_2_14_a' }, [{ chessId: 't_plain', row: 9, col: 4 }]);
const t = h.unit('t_plain');
h.b.addBuff(t, { key: 'test:healFree', status: 'healFree', flags: { noHeal: true, healFree: true } });
t.hp = 4000;
h.run(0.6);
const { gained, expected } = regenOver(h, t, 2);
assert.ok(gained > 0 && Math.abs(gained - expected) <= 1.5, `under 禁疗: +${gained} ≈ ${expected}`);
assert.equal(h.b.heal(p, t, 100), 0, 'a heal from her: refused');
assert.equal(h.b.heal(t, t, 100, { self: true }), 0, 'its own heal: 0 under 禁疗');
checkInvariants(h.b);
});
test('治疗加成 does not scale 生命回复速度: a healing-received multiplier and 百炼嘉维尔\'s 医学背景 (a heal hook) leave the regeneration as it is; a direct heal is scaled', () => {
const { h, p } = field({ chessId: 'chess_char_2_14_a' }, [{ chessId: 't_plain', row: 9, col: 4 }, { chessId: 'chess_char_4_23_a', row: 12, col: 4 }]);
const t = h.unit('t_plain'), gv = h.unit('chess_char_4_23_a');
h.b.addBuff(t, { key: 'test:healUp', mods: { healingTakenMul: 1.5 } });
t.hp = 4000;
gv.hp = gv.s.maxHp * 0.3; // 医学背景: healing received ×1.4 below half HP
h.run(0.6);
const v = p.s.atk * p.def.talents[0].bb.atk_to_hp_recovery_ratio;
for (const x of [t, gv]) {
assert.ok(Math.abs(x.s.hpRegen - x.base.hpRecoveryPerSec - v) < 1e-6, `${x.defId}: 生命回复速度 +ATK × ratio, unscaled`);
const { gained, expected } = regenOver(h, x, 2);
assert.ok(Math.abs(gained - expected) <= 1.5, `${x.defId}: +${gained} ≈ ${expected} (no ×1.5 / ×1.4)`);
}
assert.ok(Math.abs(h.b.heal(p, t, 100) - 150) < 1e-6, 'a heal: ×1.5 healing received');
assert.ok(h.b.heal(p, gv, 100) > 100 + 1e-6, 'a heal on 百炼嘉维尔: her 医学背景 applies');
checkInvariants(h.b);
});