feedback5: merge WE1 — fidelity (sim rules): 秘术师 store at the attack check (深靛; 维伊 / 黑键 plug into it), enemy attacks land at their damage frame so a short stun / freeze / sleep / 浮空 / 缴械 / 恐惧 before it cancels the swing, 武者 heal per damage instance through 禁疗, 庇护 the strongest only, PHY-X 不高于 50 %, 链愈师 jumps in the 3×3, a summon readied at its owner's redeploy, Misery's duration skills, 伺夜 S2 in 战术点形态, centurions hit up to block-count targets; merge fixes: 黑键's kit keeps the up() import WE2's S2 cast needs (WE1 dropped it with its tick store — the first regeneration had stored 9288 swallowed errors in diy-114), the 自选 refusal names 集成战略 / 生息演算, tools/golden.mjs prints a missing value, test/golden.test.js fails on any stored engine error; golden: vs WE1 only the W4B / WE2 scenarios differ (roster-042, bond-egirShip-high, coop2-ABYSS-10-boosted, coop2-NORMAL-70-diy, diy-045 / 078 / 114 / 115 / 116)

This commit is contained in:
sganggs committed 2026-10-06 16:52:11 +08:00
commit e2bb090f44
60 files changed
+6888 -6103

No files matched your search

+14 -26
View File
@@ -27,8 +27,9 @@
// prosts_t_1[born_charge] BLOCK_CNT ×0, interval 0.1, firstTriggerInterval 1, FixedValueDamage PURE `damage` on itself);
// buff_template_data (monstr_t_2[atk_spd] ON_OUTPUT_MODIFIER IsHeal; monstr_s_3[undead] ON_BEFORE_TRY_SET_HP_ZERO).
// - Trait (链愈师) "恢复友方单位生命,且会在3个友方单位间跳跃,每次跳跃治疗量降低25%" — the chain is this kit's (the profession's
// 2.5-tile chain is replaced: heal mode single + `chainFrom`), as her heal projectiles have it: after the heal on the main
// target the heal jumps to the lowest-HP-ratio injured ally of the 3×3 around the last one healed (x-4), each jump ×
// chain is replaced: heal mode single + `chainFrom`, its links the profession's — ai.js chainHealNext): after the heal on
// the main target the heal jumps to the lowest-HP-ratio ally (a full-HP one too — PRTS 分支特性信息 链愈师; ties: the latest
// deployed) of the 3×3 around the last one healed (x-4), each jump ×
// attack@chain.atk_scale (0.75; XAH-X 0.85), up to attack@chain.max_target units healed (3). A 重构体 (and she herself while S3
// runs: monstr[free_jump]) is a free link: healing it uses no target of the count and the next jump does not decay (XAH-X
// stage 3: the next attack@chain.extra_cnt jumps after that neither). Her heals pass 禁疗 ("治疗效果无视禁疗"); her selection
@@ -54,8 +55,9 @@
// - S3 策略:熔毁 (MANUAL, data DEFAULT, 25 s; only while her 重构体 stands — the cast is refused otherwise): the 重构体 is withdrawn
// (its piece waits: back once its tile is free again) and she 【移动】s onto its tile; meanwhile range x-4, ATK +atk, base attack
// time base_attack_time (a flat −1.5 s on 2.85 s), block +block_cnt, max HP +max_hp (the HP ratio kept), 流失 of
// damage_per_second HP/s (8 every 0.1 s); her attacks hit every ground enemy she blocks (one in range when she blocks none —
// [ASSUMED] as 凯尔希's Mon3tr S2) for ATK true damage, and each attack starts a chain heal on herself at attack@heal_scale × ATK
// damage_per_second HP/s (8 every 0.1 s); "同时攻击阻挡的所有敌人" (her selector's _limitedMaxTargetNumToBlockedCnt): each attack
// takes up to her block count of ground enemies of her range, the blocked ones first (ai.js `hitAllBlocked`, the 强攻手 rule
// of PRTS 分支特性信息) for ATK true damage, and each attack starts a chain heal on herself at attack@heal_scale × ATK
// (she is a free link). 不死: a hit or 流失 that would knock her out leaves her at 1 HP, untargetable and blocking nothing, and
// the skill ends on the next tick. At the end she 【返回】s to the tile she left (her SP emptied; the max HP drop keeps the ratio,
// so after a fatal blow she stands at ≤ 1 HP) with 1 s of 不死. The 路标形态 marker on her tile is not modelled: should another
@@ -63,6 +65,7 @@
import { num, talentBb, traitBb, skillRec, batMod, installAura, up } from '../shared/tier1.js';
import { COLS } from '../../../constants.js';
import { chainHealNext } from '../../../ai.js';
const S1 = 'skchr_monstr_1';
const S2 = 'skchr_monstr_2';
@@ -101,28 +104,13 @@ const prostsInRange = (battle, unit) => prostsOf(battle, unit).find((t) => up(t)
/** `a` stands on the 3×3 around `b` (x-4: a heal jump's reach, the 重构体's aura). */
const nearX4 = (a, b) => Math.abs(a.tileR - b.tileR) <= 1 && Math.abs(a.tileC - b.tileC) <= 1;
/**
* The next link of a chain heal from `cur`: the lowest-HP-ratio injured ally of its 3×3 not healed yet by this chain (her
* own 重构体 even at full HP when `fullProsts`); never a 禁疗 unit but her 重构体.
*/
function nextLink(battle, unit, cur, seen, fullProsts) {
let best = null;
for (const a of battle.allyUnits) {
if (!a.alive || !a.deployed || a.hidden || a.kind === 'device' || seen.has(a.id) || !nearX4(a, cur)) continue;
const mine = isProstsOf(a, unit);
if (a !== unit && ((a.s.flags.noHeal && !mine) || (a.profile && a.profile.noHeal))) continue;
if (!(a.hp < a.s.maxHp - 1e-6) && !(fullProsts && mine)) continue;
if (!best || a.hpRatio < best.hpRatio - 1e-12 || (Math.abs(a.hpRatio - best.hpRatio) <= 1e-12 && a.deploySeq < best.deploySeq)) best = a;
}
return best;
}
/**
* A chain heal of hers from `main` (already healed by her attack when `mainDone`): up to `count` units healed, × `step` per
* jump; a free link (her 重构体; herself with `selfFree`) uses no target of the count and the next 1 + `extra` jumps keep the
* amount.
* amount. Each link is the 链愈师 one (ai.js chainHealNext — the 3×3 around the last one healed, the lowest HP ratio a
* full-HP ally included, then the latest deployed; no 禁疗 unit but her 重构体, her `healThrough`).
*/
function chainFrom(battle, unit, main, base, { count, step, extra, fullProsts = false, mainDone = false, selfFree = false }) {
function chainFrom(battle, unit, main, base, { count, step, extra, mainDone = false, selfFree = false }) {
const free = (t) => isProstsOf(t, unit) || (selfFree && t === unit);
let amount = base;
if (!mainDone) battle.heal(unit, main, amount, HEAL_OPTS);
@@ -131,7 +119,7 @@ function chainFrom(battle, unit, main, base, { count, step, extra, fullProsts =
const seen = new Set([main.id]);
let cur = main;
while (counted < count) {
const next = nextLink(battle, unit, cur, seen, fullProsts);
const next = chainHealNext(battle, unit, cur, seen);
if (!next) break;
seen.add(next.id);
if (calm > 0) calm--; else amount *= step;
@@ -212,7 +200,7 @@ export default {
kind: 'duration',
mods: { atkPct: num(b3.atk), batPct: batMod(b3.base_attack_time, chess), blockCnt: num(b3.block_cnt), hpFlat: num(b3.max_hp) },
targeting: s3?.rangeGrid ? { rangeGrid: s3.rangeGrid } : undefined,
attack: { dmgType: 'true', attack: 'melee', projectile: 'none', canHitFly: false, groundOnly: true, hitAllBlocked: true, maxTargets: 1 },
attack: { dmgType: 'true', attack: 'melee', projectile: 'none', canHitFly: false, groundOnly: true, hitAllBlocked: true },
onStart({ battle, unit, skill }) {
const r = prostsOf(battle, unit).find(up);
if (!r) { skill.end('noToken'); skill.addCharge(1); return; } // (refused before: the activate guard)
@@ -280,12 +268,12 @@ export default {
const base = unit.s.atk * (prof.atkScale ?? 1) * (prof.healScale ?? 1) * unit.s.atkScaleMul;
for (const t of c.targets || []) {
if (!t || t.side !== 'ally') continue;
chainFrom(battle, unit, t, base, { ...chain, count: s1on ? s1Count : chain.count, fullProsts: s2on, mainDone: true });
chainFrom(battle, unit, t, base, { ...chain, count: s1on ? s1Count : chain.count, mainDone: true });
// S2: 0.6 s after her attack healed her 重构体, a second chain on it (her ATK × 1)
if (s2on && isProstsOf(t, unit)) {
battle.after(S2_EXTRA_DELAY, () => {
if (!unit.alive || !up(t)) return;
chainFrom(battle, unit, t, unit.s.atk * unit.s.atkScaleMul, { ...chain, fullProsts: skillOn(unit, S2) });
chainFrom(battle, unit, t, unit.s.atk * unit.s.atkScaleMul, chain);
}, { owner: unit });
}
}