feedback5 WE1: 链愈师 heal jumps within the 3×3 around the last one healed (x-4), a full-HP ally included (follow-up 27)

PRTS 分支特性信息 链愈师: "跳跃范围为x-4…不会对已跳跃过的单位重复跳跃", "优先跳跃至范围内生命比例最低>部署时间点最晚的我方单位。
可选择满生命我方单位为跳跃目标,但仍受禁疗制约" (range_table x-4 = the 3×3 around the unit).

- ai.js chainHealNext: an ally on the 3×3 of tiles around the last one healed, not yet in the chain, no device / hidden /
  孤立 / 禁疗 unit (the healer's healThrough summon excepted), the lowest HP ratio first — a full-HP ally too, the chain
  goes on from it — then the latest deployed. doHeal (莎草) and Mon3tr's chainFrom both use it (her fullProsts case is
  now general and gone). It used to be the most injured ally within 2.5 tiles. [ASSUMED] 禁疗 = not selected.

Golden: roster-004, roster-005, roster-023, roster-024, bond-sargonShip-low (莎草's chains: other jump targets, full-HP
links); diy-133, diy-134 (Mon3tr). Others unchanged (no match fields a chain healer).

Tests: test/sim/feedback5-chain-heal.test.js (new, 3); op_monstr, kits_t1t2, feedback2-surtr-stealth pass.
Docs: SIM.md §Professions (chainhealer row).
This commit is contained in:
sganggs committed 2026-10-06 16:00:20 +08:00
1 parent 027c8477eb
commit e1a7920482
7 files changed
+227 -166

No files matched your search

+22 -7
View File
@@ -333,6 +333,27 @@ export function resolveHit(b, u, prof, target, info, x, y) {
}
}
/**
* The next unit of a 链愈师 heal chain of `healer` from `prev` — PRTS 分支特性信息 链愈师: "跳跃范围为x-4,无特殊说明的场合一次
* 治疗链不会对已跳跃过的单位重复跳跃", "优先跳跃至范围内生命比例最低>部署时间点最晚的我方单位。可选择满生命我方单位为跳跃目标,但仍受
* 禁疗制约": an ally on the 3×3 of tiles around prev's (range x-4), not yet in this chain (`seen` ids), no device, no hidden
* or 孤立 unit, no 禁疗 / 无法被治疗 one (as Battle.injuredAlliesInKeys: the healer's `healThrough` lets its own summon in —
* 凯尔希 / Mon3tr); the lowest HP ratio first — a full-HP ally too, healed for nothing, and the chain jumps on from it —,
* then the latest deployed (aggroSeq). Null when none. Shared by the profession (doHeal) and Mon3tr's kit.
*/
export function chainHealNext(b, healer, prev, seen) {
const through = healer && healer.profile && typeof healer.profile.healThrough === 'function' ? healer.profile.healThrough : null;
const r0 = prev.tileR, c0 = prev.tileC;
let best = null;
for (const a of b.allyUnits) {
if (!a.alive || !a.deployed || a.hidden || a.kind === 'device' || seen.has(a.id)) continue;
if (Math.abs(a.tileR - r0) > 1 || Math.abs(a.tileC - c0) > 1) continue;
if (a !== healer && (a.s.flags.isolated || (a.s.flags.noHeal && !(through && through(healer, a))) || (a.profile && a.profile.noHeal))) continue;
if (!best || a.hpRatio < best.hpRatio - 1e-12 || (Math.abs(a.hpRatio - best.hpRatio) <= 1e-12 && a.aggroSeq > best.aggroSeq)) best = a;
}
return best;
}
function doHeal(b, u, prof, t) {
const atk = u.s.atk;
const scale = (prof.atkScale ?? 1) * (prof.healScale ?? 1) * u.s.atkScaleMul;
@@ -346,13 +367,7 @@ function doHeal(b, u, prof, t) {
let prev = t;
const n = Math.max(1, h.count || 3);
for (let k = 1; k < n; k++) {
let best = null, bd = Infinity;
for (const a of b.alliesInRadius(prev.x, prev.y, 2.5, null)) {
// 禁疗 / noHeal units are no heal target for the bounces either (as injuredAlliesInKeys; 史尔特尔's 余烬, GitHub #52)
if (seen.has(a.id) || a.hp >= a.s.maxHp || a.kind === 'device' || a.s.flags.noHeal || (a.profile && a.profile.noHeal)) continue;
const d = a.hpRatio;
if (d < bd) { bd = d; best = a; }
}
const best = chainHealNext(b, u, prev, seen);
if (!best) break;
seen.add(best.id);
b._ev(['atk', prev.id, best.id, 'chainHeal']);