feedback5 K2: stand-in kits for Stormeye, Pith and Raidian — every skill, talent and module at every form

Stormeye (char_611_acnipe; 空弦, 莫斯提马, 百炼嘉维尔 / 妮芙, 蕾缪安 / 异客): 风坠 rolls on every attack instance
(acnipe_t_1: ATK × atk_scale before DEF; S3 talent@prob), 风雨欲来 +0.2 SP/s after `delay` s without an attack
[ASSUMED reading], S1 ATK / ASPD / 100 DEF ignored, S2 an endless toggle (ATK +5 %, 2 targets), S3 3 targets × double
hit plus one physical ATK × 1.0 instance on a target at >= 90 % HP on the attack's first hit (acnipe_s_3[extra], no
风坠 / MAR-X on it); MAR-X stays the fastshot profile's flyScale.

Pith (char_612_accast; 烛煌, 焰影苇草 / 迷迭香 / 荒芜拉普兰德): splash from the trait blackboard, 12 RES ignored, ATK
+10 / +20 % on herself and the 【术师】 of the 4 tiles next to her, S1 ASPD + splash 1.5, S2 ASPD + ATK on every 【术师】,
S3 one ATK × atk_scale_aoe arts hit on every enemy of her range at the cast, then 2 targets, ASPD, splash 1.2 (PRTS
备注); SPC-X range (moduleRangeUp).

Raidian (char_614_acsupo; 灵知, 夕 / 安洁莉娜, 浊心斯卡蒂 / 仇白): 同调 on herself and the operators next to her (×
talent_scale in S3), 诱引 per PRTS 命中率 — one roll per enemy attack in her range, a miss cancels the whole attack
before the enemy riders (hit priority 300), S1 / S2 from the blackboards (S2 flat −0.1 / −0.2 s, 停顿 1.1 / 1.2 s), S3
y-4 + ATK + 脆弱 / 虚弱 aura with the 虚弱 stacking as 90 / 80 % (stackAs); S3 casts by the data's ACTIVE_RANGE;
DEC-X +0.2 SP/s with an enemy in range.

Triggers stay the data's (AUTO attack buffs Stormeye S2 / Pith S1 keep DEFAULT, as the W5C AUTO audit). No golden
scenario fields a stand-in: digests unchanged. Tests: test/content/standin_acnipe / _accast / _acsupo (27).
This commit is contained in:
sganggs committed 2026-10-05 18:34:46 +08:00
1 parent eac9b9737d
commit f317adf003
7 files changed
+1094 -1

No files matched your search

+1 -1
View File
@@ -64,7 +64,7 @@ export const KIT_FILES = Object.freeze([
* A stand-in without a file fights with the generic kit (content/generic.js) plus its unconditional stat talents
* (genericTalents) — the eight 预备干员 need no file.
*/
export const STANDIN_KIT_FILES = Object.freeze([]);
export const STANDIN_KIT_FILES = Object.freeze(['standin-acnipe.js', 'standin-accast.js', 'standin-acsupo.js']);
async function loadKitFile(file) {
try {
@@ -0,0 +1,97 @@
// server/sim/content/kits/ops/standin-accast.js — Pith (char_612_accast) 补位 stand-in kit: 6★ 扩散术师 for 烛煌 (T5) and
// 焰影苇草 / 迷迭香 / 荒芜拉普兰德 (T6), all S3 + SPC-X; every skill at every form.
// Kit contract and the stand-in rules: ../README.md ("Stand-in kits").
//
// Sources: data/backups.json (character_table / skill_table / battle_equip_table at E2 Lv1 rank 4 and E2 Lv60 rank 7),
// PRTS Pith(卫戍协议) (S3 备注 "※溅射范围扩大至1.2") and the client's battle data (charpack char_612_accast,
// battle/buff_template_data.json accast_s_3[AOE]).
// - 扩散术师 trait "攻击造成群体法术伤害": the splash around each struck target, radius = the trait's
// attack@projectile_range (1.1, PRTS 溅射半径一览's branch value); a skill's attack@projectile_range replaces it while it runs.
// - T1 "见我所见" "攻击无视目标12法术抗性": magic_resist_penetrate_fixed — a flat 法术穿透 attribute (charpack accast_t_1:
// attribute 27 from the blackboard; PRTS Pith 备注 "无视法抗效果为增加自身法术穿透属性(直接加算)"): every arts damage she deals.
// - T2 "授我所授" "自身及相邻四格的【术师】干员攻击力+10%" (module SPC-X level 2 / 3: +15 / +20 %): an aura on her own tile and
// the 4 orthogonal ones, 【术师】 operators only (charpack: profession mask CASTER, removed when the target leaves);
// several sources keep the strongest (installAura, [ASSUMED] like every same-named talent aura of the mode).
// - S1 "书我所书" (自动触发, 25 s): ASPD +attack_speed, splash radius attack@projectile_range (1.5). Trigger: the data's
// DEFAULT — an AUTO attack buff waits for her next attack (W5C's AUTO audit: "attack modes / attack buffs" keep waiting).
// - S2 "为我所为" (35 s): her ASPD +attack_speed; "所有【术师】干员攻击力+atk" — an aura on every 【术师】 operator of the field
// (her included) while it runs, refreshed every AURA_IV s; the strongest of several sources applies [ASSUMED].
// - S3 "驭我所驭" (35 s): at the cast, one atk_scale_aoe × ATK arts hit on every enemy in her attack range (accast_s_3[AOE]:
// AOEDamage MAGICAL, its s3_selector; a skill hit, not an attack); then attack@max_target targets (+1), ASPD
// +attack_speed, splash attack@projectile_range (1.2, PRTS 备注).
// - Module SPC-X "攻击范围扩大": her range becomes the module's own grid (the 3×3 caster range + the tile [0,3]; shared
// tier5 moduleRangeUp), so the S3 burst covers it too; HP / ATK in the stats; T2 +20 % at level 3 (composed talents).
import { num, talentBb, skillRec, statBuff, installAura, up } from '../shared/tier1.js';
import { moduleRangeUp } from '../shared/tier5.js';
const S1 = 'skchr_accast_1';
const S2 = 'skchr_accast_2';
const S3 = 'skchr_accast_3';
/** Refresh period / length of the S2 aura buff (the non-stacking aura convention of the kits). */
const AURA_IV = 0.25;
const AURA_DUR = 0.4;
const S2_KEY = 'accast:s2';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
/** A 【术师】 operator (summons are never 【术师】 operators). */
const isCaster = (a) => !!a && a.kind === 'op' && a.def?.profession === 'CASTER';
/** One of the 4 tiles next to `u` (相邻四格). */
const adjacent4 = (a, u) => Math.abs(a.tileR - u.tileR) + Math.abs(a.tileC - u.tileC) === 1;
/** S2: ATK +v on every 【术师】 operator unless a stronger one from another source is on it. */
function casterAtk(battle, unit, v) {
if (!(v > 0)) return;
for (const a of battle.alliesFor(unit)) {
if (!isCaster(a)) continue;
const cur = a.findBuff(S2_KEY);
if (cur && cur.source !== unit && (cur.data?.v ?? 0) > v && cur.timeLeft > 0.05) continue;
battle.addBuff(a, { key: S2_KEY, duration: AURA_DUR, mods: { atkPct: v }, source: unit, data: { v }, tags: ['aura'] });
}
}
export default {
char_612_accast: (bb, chess) => {
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
const tb = chess?.trait?.bb ?? {};
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
return {
trait: { splashRadius: num(tb['attack@projectile_range'], 1.1) },
skills: {
[S1]: { kind: 'duration', mods: { aspd: num(b1.attack_speed) }, attack: { splashRadius: num(b1['attack@projectile_range'], 1.5) } },
[S2]: {
kind: 'duration',
mods: { aspd: num(b2.attack_speed) },
onStart({ battle, unit }) { casterAtk(battle, unit, num(b2.atk)); },
},
[S3]: {
kind: 'duration',
mods: { aspd: num(b3.attack_speed) },
targeting: { maxTargets: Math.max(1, Math.floor(num(b3['attack@max_target'], 2))) },
attack: { splashRadius: num(b3['attack@projectile_range'], 1.2) },
onStart({ battle, unit }) {
const scale = num(b3.atk_scale_aoe, 1);
for (const e of battle.enemiesInKeys(unit.rangeKeys, unit, unit.profile)) {
battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'arts', isSkill: true, tags: ['skill', 'accast:aoe'] });
battle.fx('aoe', { x: e.x, y: e.y, r: 0.5, id: unit.id, skill: 'accast' });
}
},
},
},
talents: [
{ install(battle, unit) { statBuff(battle, unit, 'talent:accast:res', { resIgnoreFlat: num(t0.magic_resist_penetrate_fixed) }); } },
{ install(battle, unit) {
const v = num(t1.atk);
if (v) installAura(battle, unit, { key: 'talent:accast', value: v, select: (a) => isCaster(a) && (a === unit || adjacent4(a, unit)), mods: { atkPct: v } });
} },
],
install(battle, unit) {
moduleRangeUp(battle, unit, chess);
if (unit.skill?.id !== S2) return;
// S2's aura while it runs (its onStart gives the first application at the cast)
const v2 = num(b2.atk);
battle.every(AURA_IV, () => { if (up(unit) && unit.skill.active) casterAtk(battle, unit, v2); }, { owner: unit });
},
};
},
};
@@ -0,0 +1,93 @@
// server/sim/content/kits/ops/standin-acnipe.js — Stormeye (char_611_acnipe) 补位 stand-in kit: 6★ 速射手 for 空弦 (T3, S3),
// 莫斯提马 (T4, S2), 百炼嘉维尔 / 妮芙 (T5, S3 + MAR-X) and 蕾缪安 / 异客 (T6, S3 + MAR-X); every skill at every form.
// Kit contract and the stand-in rules: ../README.md ("Stand-in kits").
//
// Sources: data/backups.json (character_table / skill_table / battle_equip_table at E2 Lv1 rank 4 and E2 Lv60 rank 7)
// and the client's battle data (charpack char_611_acnipe, battle/buff_template_data.json — the templates quoted below).
// - 速射手 trait 优先攻击空中单位 (data `targetPriority` 'fly', `canHitFly`); module MAR-X "攻击空中单位时攻击力提升至110%"
// (trait atk_scale 1.1, template acnipe_e_002_trait: CheckMotionMode FLY → AtkScaleUp on every damage calculation) is
// the fastshot profile's own (professions.js fastshot `flyScale` from the trait blackboard): no kit code. The module's
// HP / ATK are in the stats; at level 3 it raises 风坠 to 190 % (the composed talents).
// - T1 风坠 "攻击时有25%的概率攻击力提升至180%" — acnipe_t_1: ON_CALCULATE_DAMAGE → Dice(prob) → AtkScaleUp(atk_scale): a roll
// on EVERY damage instance of an attack (each target, each hit of S3's double hit), ATK × atk_scale before DEF / RES.
// S3 raises `prob` to its talent@prob.
// - T2 风雨欲来 "自身未进行攻击时,技力回复速度+0.2/秒" (sp_recovery_per_sec, delay 2): +0.2 SP/s once she has made no attack
// for `delay` s — [ASSUMED] the reading of `delay` (no text or PRTS note gives it), counted from her deployment too.
// - S1 破空: ATK +atk, ASPD +attack_speed, 无视 def_penetrate_fixed DEF (defIgnoreFlat) for its 20 s.
// - S2 心手合一 (自动触发, 持续时间无限): ATK +atk and attack@max_target targets until she is knocked out (a toggle). Trigger:
// the data's DEFAULT — an AUTO attack buff waits for her next attack like 能天使 S3 / 乌尔比安 S2 (W5C's AUTO audit:
// "attack modes / attack buffs" keep waiting); being endless it changes nothing but the moment of the cast.
// - S3 旋臂: attack@max_target targets, attack@times hits each (二连击), 风坠 at talent@prob; and "攻击目标生命值高于90%时额外
// 造成1次伤害": the S3 attack carries acnipe_s_3[extra] (`_onlyFeedActiveBuffToFirstOne`: the first hit of the double hit
// only) — FilterByTargetHpRatio GE attack@hp_ratio, then AdvancedApplyDamage PHYSICAL attack@atk_scale_extra × ATK with
// `_emitSourceOnCalculateDamage` false: one extra physical instance per target per attack, which neither 风坠 nor the
// MAR-X trait touches (no isAttack: no "攻击时" effect sees it). [ASSUMED] the HP ratio is read when that first hit lands,
// before its damage (as 异客's 机理分析 kit reads it; the order of the buff and the hit is not in the data).
import { num, talentBb, skillRec, spTimeBonus } from '../shared/tier1.js';
const S1 = 'skchr_acnipe_1';
const S2 = 'skchr_acnipe_2';
const S3 = 'skchr_acnipe_3';
/** Tag of S3's extra damage instance (not an attack: 风坠 skips it). */
const EXTRA = 'acnipe:extra';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const count = (v, d) => Math.max(1, Math.floor(num(v, d)));
const s3On = (unit) => !!(unit.skill && unit.skill.active && unit.skill.id === S3);
export default {
char_611_acnipe: (bb, chess) => {
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
return {
skills: {
[S1]: { kind: 'duration', mods: { atkPct: num(b1.atk), aspd: num(b1.attack_speed), defIgnoreFlat: num(b1.def_penetrate_fixed) } },
[S2]: { kind: 'toggle', mods: { atkPct: num(b2.atk) }, targeting: { maxTargets: count(b2['attack@max_target'], 2) } },
[S3]: {
kind: 'duration',
targeting: { maxTargets: count(b3['attack@max_target'], 3) },
attack: {
hits: count(b3['attack@times'], 2),
// the extra instance, after the attack's hits on that target (marked by the install's first-hit check)
onEachHit({ battle, unit, target, kind }) {
const due = unit.mem.acnipeExtra;
if (kind !== 'main' || !due || !due.has(target)) return;
due.delete(target);
if (target.alive) battle.dealDamage(unit, target, { amount: unit.s.atk * num(b3['attack@atk_scale_extra'], 1), type: 'phys', isSkill: true, tags: ['skill', EXTRA] });
},
},
},
},
talents: [
{ install(battle, unit) { // 风坠: every damage instance of an attack rolls
const scale = num(t0.atk_scale, 1), base = num(t0.prob);
if (!(scale > 0) || scale === 1) return;
battle.on('hit', (ctx) => {
const d = ctx.dmg;
if (ctx.source !== unit || !ctx.target || ctx.target.side !== 'enemy' || !d.isAttack || d.type === 'element' || (d.tags || []).includes(EXTRA)) return;
const p = s3On(unit) ? num(b3['talent@prob'], base) : base;
if (p > 0 && battle.rng.chance(p)) d.amount *= scale;
}, { owner: unit });
} },
{ install(battle, unit) { // 风雨欲来: no attack for `delay` s (her deployment counts as the start) ⇒ +SP/s
const delay = num(t1.delay, 0);
spTimeBonus(battle, unit, num(t1.sp_recovery_per_sec), () => battle.time - Math.max(unit.lastAttackAt, unit.deployedAt) >= delay - 1e-9);
} },
],
install(battle, unit) {
if (unit.skill?.id !== S3) return;
// S3 [extra]: the first hit of each attack on a target checks its HP ratio before that hit's damage
const ratio = num(b3['attack@hp_ratio'], 0.9);
battle.on('hit', (ctx) => {
const t = ctx.target, d = ctx.dmg;
if (ctx.source !== unit || !t || t.side !== 'enemy' || !d.isAttack || d.isSplash || !s3On(unit)) return;
const seen = unit.mem.acnipeSeen ??= new WeakMap();
if (d.attackId && seen.get(t) === d.attackId) return;
seen.set(t, d.attackId);
if (t.hpRatio >= ratio - 1e-9) (unit.mem.acnipeExtra ??= new Set()).add(t);
}, { owner: unit });
},
};
},
};
@@ -0,0 +1,156 @@
// server/sim/content/kits/ops/standin-acsupo.js — Raidian (char_614_acsupo) 补位 stand-in kit: 6★ 凝滞师 for 灵知 (T4, S2),
// 夕 / 安洁莉娜 (T5, S3 + DEC-X) and 浊心斯卡蒂 / 仇白 (T6, S3 + DEC-X); every skill at every form.
// Kit contract and the stand-in rules: ../README.md ("Stand-in kits").
//
// Sources: data/backups.json (character_table / skill_table / battle_equip_table at E2 Lv1 rank 4 and E2 Lv60 rank 7),
// PRTS Raidian(卫戍协议) (S3 备注 on its 虚弱), PRTS 命中率 and the client's battle data (charpack char_614_acsupo).
// - 凝滞师 trait "攻击造成法术伤害,并对敌人造成短暂的停顿": arts, 停顿 `sluggish` s on every target (professions.js slower,
// from the trait blackboard); S2 lengthens it (attack@sluggish).
// - T1 同调 "攻击速度+15,相邻的干员攻击速度+10" (module DEC-X level 2 / 3: 18 / 11, 20 / 12): her own ASPD (charpack
// acsupo_t_1[self]) and an aura on the operators of the 4 tiles next to her (acsupo_t_1[ally]: profession mask of every
// operator class — no summon —, removed when the target leaves); several sources keep the strongest [ASSUMED]. S3
// multiplies both by talent_scale (the S3 mode's `_applyTalentScale`).
// - T2 诱引 "攻击范围内敌人的物理和法术命中率降低10%" (damage_hitrate_physical / damage_hitrate_magical): PRTS 命中率 — every
// unit has 100 % 物理 / 法术命中率; an attack of that damage type made at a lower rate rolls once when it starts, and a
// failed roll misses as a whole: every target and hit of that attack, its splash, and the effects it would carry
// ("攻击未命中…在输出伤害事件前就算作处理失败", no 晕眩 / 停顿 …). Here: one roll per enemy attack (its `attackId`) while the
// attacker stands in a Raidian's current attack range (S3's included), made by a battle-wide `hit` handler at
// LURE_PRIORITY — before the enemy content's own hit riders (content/enemies.js, priority 200) and everything else —
// which cancels every damage instance of a missed attack and stops the hook there. A target's own 闪避 is a separate roll
// in the damage pipeline. [ASSUMED] the roll is made at the attack's first damage instance (a ranged enemy's at its
// projectile's arrival; it stands still through its attack clip) rather than at its wind-up; two Raidians' cuts never
// add up (the stronger one counts, like the mode's other same-named effects — the charpack's `disableOverride` might
// mean they coexist); true damage is never affected (PRTS: neither 物理 nor 法术).
// - S1 双声 (24 s): ATK +atk, attack@max_target targets.
// - S2 三形 (14 / 17 s): base attack time + base_attack_time s (PRTS "略微缩短(-0.1)" / "缩短(-0.2)": a flat change, batMod),
// attack@max_target targets, 停顿 attack@sluggish s.
// - S3 信号跃动 (30 / 35 s): range y-4 (the skill's rangeGrid) — its auto-cast is the data's ACTIVE_RANGE (the owner's rule
// of 2026-10-05: an enemy inside the larger range casts it; tools/build-data.mjs resolveTrigger, not this kit) —, ATK
// +acsupo_s_3.atk, T1 × talent_scale, and every enemy in her attack range takes 脆弱 damage_scale − 1 and 虚弱 1 − atk:
// the catalogue statuses (同名效果取最高), refreshed every AURA_IV s while the skill runs. The 虚弱 stacks as `atk`
// (0.9 / 0.8: PRTS 备注 "此技能的虚弱在叠加时视为90%(1级~6级)/80%(7级~专精二)…的虚弱(仅影响叠加优先级,不影响实际效果)"
// — applyStatus `stackAs`), so it outranks every weaker 虚弱 while it lasts.
// - Module DEC-X "攻击范围内存在敌人时技力自然恢复速度+0.2/秒" (hidden talent sp_recovery_per_sec; acsupo_e_002_t: one
// +0.2 whatever the number of enemies); HP / ATK in the stats; T1 changes at level 2 / 3 (composed talents).
import { canTargetEnemy } from '../../../targeting.js';
import { bodyInKeys } from '../../../body.js';
import { num, talentBb, moduleBb, skillRec, statBuff, spTimeBonus, enemyInRange, up, batMod } from '../shared/tier1.js';
const S1 = 'skchr_acsupo_1';
const S2 = 'skchr_acsupo_2';
const S3 = 'skchr_acsupo_3';
/** Refresh period / length of the auras (T1 on allies, S3 on enemies): the non-stacking aura convention of the kits. */
const AURA_IV = 0.25;
const AURA_DUR = 0.4;
const T1_KEY = 'acsupo:t1';
/** 诱引's battle-wide `hit` handler runs before every other one (the enemy content's riders sit at 200). */
const LURE_PRIORITY = 300;
const ANY = Object.freeze({ canHitFly: true });
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const count = (v, d) => Math.max(1, Math.floor(num(v, d)));
const adjacent4 = (a, u) => Math.abs(a.tileR - u.tileR) + Math.abs(a.tileC - u.tileC) === 1;
const s3On = (unit) => !!(unit.skill && unit.skill.active && unit.skill.id === S3);
// ---- 诱引 (T2): one battle-wide handler over every Raidian of the battle -------------------------------------------
/** battle → { srcs: [{ unit, phys, arts }], last: WeakMap(enemy → { id, miss }) }. */
const LURE = new WeakMap();
/** The hit-rate cut (0–1) `e`'s attack of damage type `type` suffers: the strongest of the Raidians whose range holds it. */
function lureCut(st, e, type) {
let cut = 0;
for (const s of st.srcs) {
const u = s.unit, v = Math.abs(type === 'arts' ? s.arts : s.phys);
if (v > cut && up(u) && u.rangeKeySet && canTargetEnemy(u, e, ANY) && bodyInKeys(e, u.rangeKeySet)) cut = v;
}
return Math.min(1, cut);
}
function lureHit(battle, st, ctx) {
const e = ctx.source, d = ctx.dmg;
if (!e || e.side !== 'enemy' || !d || !d.isAttack || d.cancel || (d.type !== 'phys' && d.type !== 'arts')) return;
// one roll per attack: every damage instance of it (each target, a multi-hit) shares the first instance's result
const prev = d.attackId ? st.last.get(e) : null;
let miss;
if (prev && prev.id === d.attackId) miss = prev.miss;
else {
const cut = lureCut(st, e, d.type);
miss = cut > 0 && battle.rng() < cut;
if (d.attackId) st.last.set(e, { id: d.attackId, miss });
}
if (!miss) return;
d.cancel = true;
ctx.stopPropagation = true;
if (ctx.target) battle.fx('dodge', { x: ctx.target.x, y: ctx.target.y, id: ctx.target.id });
}
function installLure(battle, unit, phys, arts) {
if (!phys && !arts) return;
let st = LURE.get(battle);
if (!st) {
st = { srcs: [], last: new WeakMap() };
LURE.set(battle, st);
// no owner: it serves every Raidian of the battle, whichever is on the field
battle.on('hit', (ctx) => lureHit(battle, st, ctx), { priority: LURE_PRIORITY });
}
st.srcs.push({ unit, phys, arts });
}
// ---- the kit ---------------------------------------------------------------------------------------------------------
export default {
char_614_acsupo: (bb, chess) => {
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
const grid3 = skillRec(chess, S3)?.rangeGrid;
const self = num(t0.attack_speed), ally = num(t0['acsupo_t_1[ally].attack_speed']);
const scale = num(b3.talent_scale, 1);
const fragile = num(b3.damage_scale, 1) - 1, atkMul = num(b3.atk, 1);
/** S3: 脆弱 / 虚弱 on every enemy of her (skill) range — at the cast, then every AURA_IV s while it runs. */
const s3Aura = (battle, unit) => {
for (const e of battle.enemiesInKeys(unit.rangeKeys, unit, ANY)) {
if (fragile > 0) battle.applyStatus(e, 'fragile', { duration: AURA_DUR, value: fragile, source: unit });
if (atkMul > 0 && atkMul < 1 && e.alive) battle.applyStatus(e, 'weaken', { duration: AURA_DUR, value: 1 - atkMul, stackAs: atkMul, source: unit });
}
};
return {
skills: {
[S1]: { kind: 'duration', mods: { atkPct: num(b1.atk) }, targeting: { maxTargets: count(b1['attack@max_target'], 2) } },
[S2]: {
kind: 'duration',
mods: { batPct: batMod(b2.base_attack_time, chess) },
targeting: { maxTargets: count(b2['attack@max_target'], 3) },
attack: { onHitStatus: { key: 'sluggish', duration: num(b2['attack@sluggish'], 1.1) } },
},
[S3]: {
kind: 'duration',
// T1 × talent_scale: her own part here, the allies' in the T1 aura
mods: { atkPct: num(b3['acsupo_s_3.atk']), aspd: self * (scale - 1) },
...(grid3 && grid3.length ? { targeting: { rangeGrid: grid3.map((p) => [p[0], p[1]]) } } : {}),
onStart({ battle, unit }) { s3Aura(battle, unit); },
},
},
talents: [
{ install(battle, unit) { // 同调
statBuff(battle, unit, 'talent:acsupo', { aspd: self });
if (!ally) return;
battle.every(AURA_IV, () => {
if (!up(unit)) return;
const v = ally * (s3On(unit) ? scale : 1);
for (const a of battle.alliesFor(unit)) {
if (a === unit || a.kind !== 'op' || !adjacent4(a, unit)) continue;
const cur = a.findBuff(T1_KEY);
if (cur && cur.source !== unit && (cur.data?.v ?? 0) > v && cur.timeLeft > 0.05) continue;
battle.addBuff(a, { key: T1_KEY, duration: AURA_DUR, mods: { aspd: v }, source: unit, data: { v }, tags: ['aura'] });
}
}, { owner: unit, immediate: true });
} },
{ install(battle, unit) { installLure(battle, unit, num(t1.damage_hitrate_physical), num(t1.damage_hitrate_magical)); } }, // 诱引
],
install(battle, unit) {
spTimeBonus(battle, unit, num(moduleBb(chess).sp_recovery_per_sec), () => enemyInRange(battle, unit)); // DEC-X
if (unit.skill?.id === S3) battle.every(AURA_IV, () => { if (up(unit) && s3On(unit)) s3Aura(battle, unit); }, { owner: unit });
},
};
},
};