feedback5: merge W5F — ACTIVE_RANGE also replaces SEARCH rows and covers 深巡 S2, 纯烬艾雅法拉 S1 at full SP, 伺夜 S1 only with his pack on the field

Golden regenerated on the merged code (only test/golden/matches.json conflicted): against feedback5 it moves exactly W5F's scenarios (roster-001/002/016/026/027/028/038/041/042/044, bond-egirShip-low, bond-suntShip-high, boss-boss_2-pair, boss-boss_6-pair, solo-NORMAL-1, coop2-NORMAL-3, coop4-FUNNY-6, coop2-NORMAL-8-serverrun); against fb5-bugs3 exactly W5E's (unite-1/2 and the 8 match scenarios of the 联防 map and the full-hand rule). data/ equals an offline build of the merged tools/build-data.mjs.
This commit is contained in:
sganggs committed 2026-10-05 19:05:06 +08:00
commit 17d9960fb4
30 files changed
+552 -407

No files matched your search

+2 -1
View File
@@ -203,7 +203,8 @@ Each item is a mistake this project already made once. Tick every one for every
`skills[].trigger`); check it, and change the data rule rather than hand-coding a trigger in the kit. The owner's
rules (0.2.0): a skill whose attack range is larger than the normal one casts as soon as an enemy is inside the
SKILL range (a deliberate deviation from the official 技能策略, like the 重装 exception: data `ACTIVE_RANGE` for a
MANUAL skill on the basic strategy whose running range strictly contains the operator's own,
MANUAL skill on the basic strategy — 深巡 S2's DEFAULT deviation included — or on the SEARCH row (薄绿 S1, 玛恩纳 S2,
安洁莉娜 S3 …) whose running range strictly contains the operator's own,
`test/sim/feedback5-active-range.test.js`; 烛煌 S3 in `feedback1e-skillrange.test.js` keeps DEFAULT — its 4-11 does
not contain her 3-1); an AUTO skill that acts on
allies or itself fires at full SP (引星棘刺 S1, `kits_alt_t5.test.js` "… fires as soon as its SP is full, no enemy
@@ -7,11 +7,13 @@ export default {
// ---------------------------------------------------------------------------------------------------------------
// 1_04 深巡 行动能力剥夺: longer line range (skill grid), ATK +atk, ASPD +attack_speed, fin darts pierce
// attack@max_target enemies on the line and cause attack@sluggish s of 停顿.
// 技能策略 → DEFAULT (PR #12; DESIGN §21.29): the official 下半 class row hands every MANUAL 重装 skill TAKE_DAMAGE,
// which makes a 2-2 ranged 哨戒铁卫 wait until something hits her — in practice until she blocks (GitHub issue #4). By
// the owner's deliberate deviation from that row (2026-10-03, community feedback) this offensive ranged skill takes
// the basic strategy (SP ready + about to attack + an enemy inside the initial range); her data says DEFAULT too
// (rawRule keeps the official TAKE_DAMAGE). Only the rule changes: spCost / initSp / spType still come from data.
// 技能策略 (DESIGN §21.29, PR #12): the official 下半 class row hands every MANUAL 重装 skill TAKE_DAMAGE, which makes a
// 2-2 ranged 哨戒铁卫 wait until something hits her — in practice until she blocks (GitHub issue #4). By the owner's
// deliberate deviation from that row (2026-10-03, community feedback) this offensive ranged skill takes the basic
// strategy (tools/build-data.mjs TRIGGER_DEVIATIONS: DEFAULT, rawRule keeps the official TAKE_DAMAGE), and since 0.2.0
// the owner's ACTIVE_RANGE rule on top of it (2026-10-05): its running range 3-2 strictly contains her 2-2, so it casts
// as soon as an enemy is inside the 3-2 — the data rule (rule ACTIVE_RANGE, customRangeGrid the 3-2), which this spec
// reads (PR #12's DEFAULT line is gone: it would have kept the 2-2).
// 细胞活性抑制剂: attacks inflict `damage` arts per `interval` s for `duration` s (damage_seamonster vs 【海怪】).
// Elite module (SPT-X): stealth of enemies inside the range is cancelled.
// Alternate S1 侵袭破坏应对 (重装 ⇒ TAKE_DAMAGE trigger from data): ATK +atk, DEF +def.
@@ -20,7 +22,6 @@ export default {
const s1 = skillBbOf(chess, 'skchr_udflow_1');
return {
skill: {
trigger: 'DEFAULT',
kind: 'duration', mods: { atkPct: num(bb.atk), aspd: num(bb.attack_speed) },
targeting: { rangeGrid: def?.skill?.rangeGrid ?? null, maxTargets: num(bb['attack@max_target'], 1) },
attack: { onHitStatus: { key: 'sluggish', duration: num(bb['attack@sluggish'], 1) } },
@@ -8,11 +8,13 @@ export default {
// pushes each one towards her, end-of-skill burst on
// every enemy in range; 地质学者: DEF aura (skill off) / less likely targeted (skill on);
// 精锐 module PLX-X: keeps part of the guard (DEF/RES) while the skill runs
// S1 风语: wider range (skill grid), attacks every enemy on it at attack@atk_scale. Auto-cast: the data
// rule SEARCH is the 阵法术师 row (PRTS 卫戍协议/帮助 "不受基础策略影响,在初始攻击范围内存在敌人时释放技能"; it
// covers every MANUAL skill of the class — user playtest #6) = an enemy inside her INITIAL range — the engine's
// DEFAULT rule, checked every tick for a unit that does not attack while its skill is off — not any enemy on the
// field: she would burn the skill on enemies that just spawned.
// S1 风语: wider range (skill grid), attacks every enemy on it at attack@atk_scale. Auto-cast: the official
// rule is the 阵法术师 row SEARCH (PRTS 卫戍协议/帮助 "不受基础策略影响,在初始攻击范围内存在敌人时释放技能"; it covers
// every MANUAL skill of the class — user playtest #6) = an enemy inside her INITIAL range, never any enemy on the
// field (she would burn the skill on enemies that just spawned). Since 0.2.0 the owner's ACTIVE_RANGE rule
// (2026-10-05) widens it: the S1 x-2 strictly contains her x-1, so the data rule is ACTIVE_RANGE on the x-2 (rawRule
// SEARCH) — an enemy she can target inside the x-2, every tick — which this spec reads. S2 keeps SEARCH (no range
// change).
chess_char_3_08_a: (bb, chess, def) => {
const d = defOf(chess, def);
const t0 = talentBb(d, 0);
@@ -38,7 +40,6 @@ export default {
const grid = copyGrid(s.rangeGrid);
return {
kind: 'duration',
trigger: { rule: 'DEFAULT' },
attack: { atkScale: num(s.bb['attack@atk_scale'], num(s.bb.atk_scale, 1)) },
...(grid ? { targeting: { rangeGrid: grid } } : {}),
onStart: guardOn,
@@ -11,8 +11,9 @@ export default {
// wolf = +1 block and one more bite, a wolf is lost instead of the pack dying); 狼群天性: DEF ignore vs pack-blocked
// enemies; S3 领袖的尊严: DP over time, 三连击, bonus arts vs pack-blocked enemies; 精锐 module: pack takes less
// damage from the enemies it blocks (token module talent). A 狼群 piece placed in the prep phase is the pack.
// S1 领袖的呼唤 (ALWAYS): +cost DP and one more “狼影” (≤ the talent's maximum); S2 领袖的馈赠: +cost DP, the pack
// recovers hp_ratio of its max HP and its next attack hits ×atk_scale — a kill by that attack gives +cost DP.
// S1 领袖的呼唤 (自动触发, only while the pack is on the field): +cost DP and one more “狼影” (≤ the talent's
// maximum); S2 领袖的馈赠: +cost DP, the pack recovers hp_ratio of its max HP and its next attack hits ×atk_scale —
// a kill by that attack gives +cost DP.
// 精锐 module TAC-Y: trait ×165 % (profession layer) and "援军阻挡的敌人更容易受到我方的攻击": the pack's token module
// talent taunt_level (+1) goes to the enemies it blocks — the enemy-side 嘲讽等级 our operators target first
// (targeting.js; research 05: "更容易受到我方的攻击" = enemy taunt.taunt_level), never to the pack itself.
@@ -114,6 +115,14 @@ export default {
return true;
};
const dpGain = (battle, unit, n) => { if (n > 0) { battle.addDp(unit.ownerId, n); fx(battle, 'dp', unit, { n }); } };
// S1: the cast — ready and the pack on the field (PRTS 备注 「仅场上存在狼群时可触发技能」)
const installCall = (battle, unit) => {
battle.on('tick', () => {
const sk = unit.skill;
if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !wolfOf(unit)) return;
sk.activate('SP_FULL');
}, { owner: unit });
};
// S2: the pack's empowered next attack (armed by the cast; ×scale on its hits; a kill pays once)
const installGift = (battle, unit) => {
battle.on('tick', () => { // the cast: ready, the pack on the field, no unused gift on it (PRTS 备注)
@@ -195,11 +204,15 @@ export default {
},
},
skills: altSkills(chess, d, bb, {
// (自动触发: an AUTO skill takes no 技能策略 — the 战术家 row is for MANUAL skills — and this DP skill fires as soon
// as it is ready, as before)
// (自动触发: an AUTO skill takes no 技能策略 — the 战术家 row is for MANUAL skills. PRTS 备注 「仅场上存在狼群时可触发技能」
// (the owner's decision of 2026-10-05): the kit casts it as soon as it is ready while the pack stands
// (installCall); without a pack it waits at full SP — until 0.2.0 it fired at SP_FULL and paid its DP with no pack.
// The 备注 also counts the pack's 战术点形态 (after its last 狼影 is lost) as on the field — the cast brings it back
// at once with one 狼影 — and at the 狼影 cap resets the pack's HP; the kit has neither: a knocked-out pack comes
// back after its respawn time and S1 waits for it, and at the cap it only pays its DP [ASSUMED])
skchr_vigil_1: (s) => ({
kind: 'instant',
trigger: 'SP_FULL',
trigger: 'NEVER',
onStart({ battle, unit }) {
dpGain(battle, unit, num(s.bb.cost, 0));
const w = wolfOf(unit);
@@ -223,7 +236,10 @@ export default {
},
}),
}),
install(battle, unit) { if (sel === 'skchr_vigil_2') installGift(battle, unit); },
install(battle, unit) {
if (sel === 'skchr_vigil_1') installCall(battle, unit);
if (sel === 'skchr_vigil_2') installGift(battle, unit);
},
talents: [
{ install() { /* 狼群领袖: the pack itself (trait.install / wolfKit) */ } },
{ install(battle, unit) {
@@ -17,7 +17,8 @@ export default withDefaults({
// S1 沙暴守卫 (SEARCH: ATK/DEF up; charged — cast with every charge stored — "特性效果在技能期间继续生效", PRTS 备注
// "应用蓄力时:应用技能未开启时的特性": the skill-off trait stays in force, the DEF/RES guard AND 不攻击 — she makes
// no attack until the skill ends, a pure guard);
// S3 食噬之印 (wider range, ATK +0 % → +140 %/+200 % in 1 s steps over 20 s — PRTS 备注; charged: each attack adds a
// S3 食噬之印 (wider range — data rule ACTIVE_RANGE on its x-2, the owner's rule of 2026-10-05 over the 阵法术师
// SEARCH row —, ATK +0 % → +140 %/+200 % in 1 s steps over 20 s — PRTS 备注; charged: each attack adds a
// stack of the mark BEFORE its damage — PRTS 备注 "于攻击造成伤害前生效,多层效果之间加算叠加" — +20 % damage from
// her per stack, ≤ 5, so ×1.2 on the first hit and ×2.0 from the 5th, until the skill ends; one mark per enemy:
// another 卡涅利安 only adds stacks to it, the bonus is the setter's — PRTS 备注 popup). Talent 生命之餐 heals on
@@ -21,7 +21,8 @@ export default {
// enemies around). T2 无动于衷: taunt +1, Kazimierz ops reflect 15 % of his ATK as true damage when attacked.
// S1 未声张的怒火 (duration, SEARCH): attacks attack@atk_scale × ATK, DEF +. S2 未宽解的悲哀 (duration): skill range,
// BAT +0.3 s, attacks attack@atk_scale × ATK twice; a kill of his own attacks during the skill keeps the trait ramp
// when it ends. S1 / S2 have no 对空 note: ground only, like his trait.
// when it ends. S1 / S2 have no 对空 note: ground only, like his trait. S2's data rule is ACTIVE_RANGE on its 2-3 (the
// owner's rule of 2026-10-05 over the 解放者 SEARCH row: an enemy he can target inside the 2-3 casts it).
chess_char_5_19_a: (bb, chess, def) => {
const t0 = talent(chess, 0), t1 = talent(chess, 1);
const sid = selectedId(chess, def);
@@ -17,7 +17,8 @@ function enemySpUp(battle, unit, key, sp) {
export default {
// ---------------------------------------------------------------------------------------------------------------
// 安洁莉娜 — slower (no normal attack while the skill is off). S3 秘杖·反重力模式 (14/18 s, SEARCH): every enemy on the
// 安洁莉娜 — slower (no normal attack while the skill is off). S3 秘杖·反重力模式 (14/18 s; her SEARCH row widened to
// ACTIVE_RANGE on its y-4 by the owner's rule of 2026-10-05: an enemy inside the y-4 casts it): every enemy on the
// field is weightless, skill range, ATK +, 4/5 targets. T1 加速力场: all allies ASPD +7. T2 兼职工作: while the skill is
// off, all allies regenerate 20 HP/s. Module (elite): longer sluggish (trait bb, profession tunables).
// S1 秘杖·速充模式 (duration, attack SP): ATK +; she attacks normally with S1 ("技能未开启时无法普通攻击" is only in the
@@ -14,10 +14,13 @@ function agoat2(bb, chess, def) {
const elemLoad = (a) => (a.elem ? a.elem.burn + a.elem.neural + a.elem.necrosis + a.elem.apoptosis + a.elem.erosion : 0);
const skills = {
// S1 无声润物 (toggle, heal): ATK +atk, one extra heal target, every ally of her range recovers ep_heal_ratio × ATK
// 元素损伤 per second
// 元素损伤 per second. 自动触发 with effects on allies only (nothing to target): on as soon as its SP is full (SP_FULL,
// the owner's decision of 2026-10-05, like 浊心斯卡蒂 S2 / 引星棘刺 S1); until 0.2.0 the data's DEFAULT heal rule kept
// it off until an ally of her range was injured, so its 元素损伤 recovery never ran on a field without HP damage
skchr_agoat2_1: {
kind: 'toggle',
heal: true,
trigger: 'SP_FULL',
mods: { atkPct: num(bb.atk) },
targeting: { maxTargets: 2 },
onStart({ unit }) { unit.mem.agoatAcc = 0; },
+4 -4
View File
@@ -17,10 +17,10 @@
// false) is honoured. Per-skill triggers come from data (the official 技能策略: every MANUAL 重装 skill ⇒ TAKE_DAMAGE —
// but the six of the owner's deliberate deviation, DESIGN §21.29 / tools/build-data.mjs TRIGGER_DEVIATIONS, which are
// DEFAULT —, a MANUAL skill with its own 技能范围 ⇒ SKILL_RANGE, a MANUAL skill on the basic strategy whose running attack
// range strictly contains the own one ⇒ ACTIVE_RANGE (the owner's rule, 2026-10-05), AUTO skills keep their own rule);
// the few spec rules are documented at the skill (冲锋号令 AUTO ⇒ SP_FULL, 花香疗法 heal-type DEFAULT; the two 哨戒铁卫
// S2s 深巡 行动能力剥夺 and 雷蛇 反击电弧 state the DEFAULT their data carries since that deviation — GitHub issue #4,
// PR #12).
// range strictly contains the own one ⇒ ACTIVE_RANGE (the owner's rule, 2026-10-05 — 深巡 S2 too, on top of its
// deviation), AUTO skills keep their own rule); the few spec rules are documented at the skill (冲锋号令 AUTO ⇒ SP_FULL,
// 花香疗法 heal-type DEFAULT; the 哨戒铁卫 S2 雷蛇 反击电弧 states the DEFAULT its data carries since that deviation —
// GitHub issue #4, PR #12; 深巡 行动能力剥夺 dropped its PR #12 line in 0.2.0 and reads its data's ACTIVE_RANGE).
// Tests: test/content/kits_alt_t1.test.js.
//
// fx kinds emitted (battle.fx(kind, {x, y, …})): aoe {radius, id, skill} · zone {radius, dur, id, skill} ·
+3 -4
View File
@@ -24,10 +24,9 @@
// parts that belong to one skill only (琳琅诗怀雅 bombs, 菲莱 counters, 雪猎 special bullets…) check the selected skill id.
// Triggers come from the data record of each skill (skills[i].trigger: tools/build-data.mjs resolveTrigger — the official
// 技能策略 incl. SKILL_RANGE for a MANUAL skill's own 技能范围 and the class rows for every MANUAL skill; ACTIVE_RANGE, the
// owner's rule of 2026-10-05, for a MANUAL basic-strategy skill whose running range strictly contains the own one), except
// 薄绿 S1 (the 阵法术师 row SEARCH = "在初始攻击范围内存在敌人时" ⇒ the engine's DEFAULT for a phalanx, checked every
// tick) and the kits' own automatic casts (雪猎 special bullets). Non-default modules: 能天使 MAR-Y (ASPD vs ground), 琳琅诗怀雅 MER-Y (ATK per payment),
// 斯卡蒂 DRE-X (× vs blocked), 瑕光 GUA-X (heal × under 50 %), 伺夜 TAC-Y (×165 % trait, pack-blocked enemies taunt +1),
// owner's rule of 2026-10-05, for a MANUAL basic-strategy or SEARCH-row skill whose running range strictly contains the
// own one — 薄绿 S1 on its x-2), except the kits' own automatic casts (雪猎 special bullets, 伺夜 S1 / S2's pack check).
// Non-default modules: 能天使 MAR-Y (ASPD vs ground), 琳琅诗怀雅 MER-Y (ATK per payment), 斯卡蒂 DRE-X (× vs blocked), 瑕光 GUA-X (heal × under 50 %), 伺夜 TAC-Y (×165 % trait, pack-blocked enemies taunt +1),
// 空弦 MAR-X (fly ×, profession layer). No in-battle effect here: 忍冬 SOL-Y "首次部署时部署费用-4" (the initial
// deployment is free) and the 集成战略-only ISW-A modules of 琳琅诗怀雅 / 空弦 (their stats and trait cost still apply).
+2 -1
View File
@@ -27,7 +27,8 @@
// the heal replaces that attack). Every other
// kit keeps the data rule (tools/build-data.mjs resolveTrigger, the official 技能策略: DEFAULT = about to attack + an
// enemy in the INITIAL range; SKILL_RANGE for a MANUAL skill's own 技能范围; ACTIVE_RANGE for a MANUAL skill on the basic
// strategy whose running attack range strictly contains the own one (the owner's rule, 2026-10-05); the class rows —
// strategy or the SEARCH row (玛恩纳 S2, 安洁莉娜 S3) whose running attack range strictly contains the own one (the
// owner's rule, 2026-10-05); the class rows —
// 重装 TAKE_DAMAGE … — for
// every MANUAL skill; AUTO skills never take a class row).
// - fx kinds emitted (battle.fx(kind, {x, y, id, …})): 'aoe' {r, skill}, 'healAoe' {r}, 'summon' {token}, 'anchor'