feedback1 final: six 重装 skills cast with an enemy in range, a deliberate deviation from the official TAKE_DAMAGE row (GitHub issue #4, PR #12)
The owner's decision (2026-10-03, after community feedback): 深巡 S2 行动能力剥夺, 雷蛇 S2 反击电弧, 号角 S2 暴风号令 and S3 终极防线, 灰毫 S1 攻击力强化·γ型 and S2 专注轰击 take the basic strategy (DEFAULT) instead of the 下半 TANK class row's TAKE_DAMAGE. Every other MANUAL 重装 skill, 深巡 S1 included, still waits for a hit. - tools/build-data.mjs TRIGGER_DEVIATIONS: a small table keyed by the normal chess id and the skill id (the elite follows through baseId), never by the skill id alone - 灰毫 S1 is the generic skcom_atk_up[3]. resolveTrigger applies it last; rawRule keeps the official TAKE_DAMAGE; validateAll fails the build when an entry no longer meets a TAKE_DAMAGE row. data/chess.json rebuilt with --offline: exactly the 12 records' rule changes. - PR #12's kit lines (trigger 'DEFAULT' on 深巡 / 雷蛇 S2) stay and agree with the data; their comments and tests now give this reason instead of the community summary. - Tests: test/sim/feedback1-tank-triggers.test.js (the data, kit = data, a real battle on 战场#01(下半) in which each of the six casts with an enemy in range before any hit and each of the other 46 MANUAL 重装 records casts on the first hit); data.test.js trigger expectations; the 灰毫 / 号角 kit tests follow; docs-consistency. - Docs: DESIGN §21.29 (new), §5.6, §19.2, §20.2, §21.20 (settled row), §21.23; DATA, SIM, PLAYING, CHANGELOG 0.1.1; research 03 §1.4 drops the arknights.wiki.gg "offensive / defensive" summary and records the activity_table history (上半 had no TANK row, 下半 added it) and this deviation; research 00-INDEX.
This commit is contained in:
1 parent
e6ab4e1ff8
commit
2dba6da598
20 files changed
+325
-58
No files matched your search
@@ -673,7 +673,8 @@ test('1_20 雷蛇 反击电弧: attack interval +70 % of the base (PRTS 增大(+
|
||||
for (const id of pair('20')) {
|
||||
const bb = raw(id).skill.bb;
|
||||
assert.equal(raw(id).skill.skillId, 'skchr_liskam_2');
|
||||
// 重装 S2 反击电弧 is an OFFENSIVE skill: the basic strategy casts it with an enemy in the skill range (issue #4) —
|
||||
// 重装 S2 反击电弧 is an OFFENSIVE skill: the basic strategy casts it with an enemy in the skill range (issue #4; the
|
||||
// deliberate deviation from the official 重装 row, DESIGN §21.29) —
|
||||
// the enemy here stands two tiles ahead and, being a speed-0 dummy, never touches her
|
||||
const h = run({ defs: { enemies: { e: dummy('e', { atk: 300, bat: 1 }) }, chess: noGarrison(id) }, units: [{ chessId: id, row: 9, col: 4, carryState: READY }], enemies: [{ key: 'e', pos: [9, 6] }] });
|
||||
const u = h.unit(id);
|
||||
|
||||
@@ -456,7 +456,7 @@ test('2_17 折桠 S1 绝境抵抗: TAKE_DAMAGE; DEF +def and 抵抗 (control sta
|
||||
}
|
||||
});
|
||||
|
||||
test('2_18 灰毫 S2 专注轰击: cast when hit (重装 TAKE_DAMAGE), then blocks nothing, ranged splash bombs only, shorter interval, ATK +atk', () => {
|
||||
test('2_18 灰毫 S2 专注轰击: cast with an enemy in range (DEFAULT, the deliberate deviation from the 重装 row — DESIGN §21.29), then blocks nothing, ranged splash bombs only, shorter interval, ATK +atk', () => {
|
||||
for (const id of both('chess_char_2_18')) {
|
||||
const bb = bbAlt(id);
|
||||
const enemies = { w: enemyRec({ key: 'w', hp: 1e7, speed: 2, atk: 50, bat: 1 }) };
|
||||
@@ -465,15 +465,18 @@ test('2_18 灰毫 S2 专注轰击: cast when hit (重装 TAKE_DAMAGE), then bloc
|
||||
assert.ok(c.runUntil(() => c.enemies()[0]?.blockedBy === c.unit(id), 10), 'default: blocked');
|
||||
done(c);
|
||||
|
||||
// (a second walker comes by while the skill runs: she bombs it from range, unblocked)
|
||||
const h = run({ defs: { enemies }, units: [U(id, 9, 5, { carryState: READY })], enemies: [{ key: 'w', route: 0 }, { key: 'w', route: 0, time: 5 }] });
|
||||
// (a second walker, 2 s behind the first, comes by while the skill runs — 10 s from the cast, made as the first one
|
||||
// comes into range: she bombs it from range, unblocked)
|
||||
const h = run({ defs: { enemies }, units: [U(id, 9, 5, { carryState: READY })], enemies: [{ key: 'w', route: 0 }, { key: 'w', route: 0, time: 2 }] });
|
||||
const u = h.unit(id);
|
||||
usesAlt(u, id);
|
||||
assert.equal(u.skill.rule, 'TAKE_DAMAGE', 'a MANUAL 重装 skill: "不受技能范围影响,受到伤害时释放技能"');
|
||||
// the official 下半 重装 row (TAKE_DAMAGE) is overridden for this skill by the owner's decision (data rawRule keeps it)
|
||||
assert.equal(u.skill.rule, 'DEFAULT', 'an offensive 重装 skill of the deviation: the basic strategy');
|
||||
assert.ok(h.runUntil(() => u.skill.active, 8));
|
||||
const st = h.b.time;
|
||||
const atk0 = h.eventsOf('atk').length;
|
||||
assert.ok(h.hooksOf('damaged').some((x) => x.target === u), 'cast by the hit of the enemy she blocked');
|
||||
assert.equal(started(h, u)[0].reason, 'DEFAULT', 'cast by the basic strategy');
|
||||
assert.ok(!h.hooksOf('damaged').some((x) => x.target === u), 'with the walker in range, before anything hit her');
|
||||
assert.ok(u.s.flags.noBlock);
|
||||
approx(u.s.atk, u.base.atk * (1 + u.findBuff('talent:ashlok').mods.atkPct + bb.atk), 'ATK (+ 炮术研习)');
|
||||
approx(u.s.interval, (u.base.bat + bb.base_attack_time) * 100 / u.s.aspd, 'base attack time shortened');
|
||||
|
||||
@@ -527,18 +527,19 @@ test('号角 S1 照明榴弹 (自动触发 ⇒ DEFAULT): the next ranged shot de
|
||||
}
|
||||
});
|
||||
|
||||
test('号角 S2 暴风号令: 10 rounds of attack@s2.atk_scale × ATK splash; the second half adds magic_atk_scale × ATK arts (过载)', () => {
|
||||
test('号角 S2 暴风号令: cast with an enemy in range (DEFAULT, DESIGN §21.29); 10 rounds of attack@s2.atk_scale × ATK splash; the second half adds magic_atk_scale × ATK arts (过载)', () => {
|
||||
for (const id of pair('08')) {
|
||||
const h = run({ defs: { enemies: { enemy_dummy: dummy('enemy_dummy') } }, units: [entry(id, 'skchr_horn_2', { row: 10, col: 2 })], enemies: [{ key: 'enemy_dummy', pos: [10, 6] }] });
|
||||
const u = sel(h, id, 'skchr_horn_2');
|
||||
const bb = bbOf(u);
|
||||
const e = h.b.enemies[0];
|
||||
h.step();
|
||||
// the owner's deliberate deviation from the 重装 TAKE_DAMAGE row (DESIGN §21.29): cast with an enemy in range
|
||||
assert.equal(u.skill.rule, 'DEFAULT');
|
||||
u.skill.gainSp(1000);
|
||||
h.run(1);
|
||||
assert.equal(u.skill.activations, 0, '重装 TAKE_DAMAGE: not before a hit');
|
||||
h.b.dealDamage(e, u, { amount: 10, type: 'phys' });
|
||||
assert.equal(u.skill.activations, 1, 'cast by the hit');
|
||||
assert.ok(h.runUntil(() => u.skill.activations === 1, 3), 'cast with the enemy in range');
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT');
|
||||
assert.ok(!h.hooksOf('damaged').some((c) => c.target === u), 'no hit needed');
|
||||
assert.equal(u.skill.kind, 'ammo');
|
||||
assert.equal(u.skill.ammoLeft + h.hooksOf('ammoUsed').filter((c) => c.unit === u).length, bb['attack@s2.trigger_time']);
|
||||
assert.ok(h.runUntil(() => !u.skill.active, 60));
|
||||
|
||||
@@ -132,7 +132,8 @@ test('1_04 深巡: fin darts pierce attack@max_target enemies on the line + 1 s
|
||||
});
|
||||
const u = h.unit(id);
|
||||
h.step();
|
||||
// 技能策略 (issue #4): an enemy inside her initial 2-2 range is enough — the basic strategy casts it, no hit required
|
||||
// 技能策略 (issue #4; the deliberate deviation of DESIGN §21.29): an enemy inside her initial 2-2 range is enough — the
|
||||
// basic strategy casts it, no hit required
|
||||
assert.equal(u.skill.rule, 'DEFAULT', '行动能力剥夺 is an offensive ranged skill, not a 重装 TAKE_DAMAGE one');
|
||||
h.run(3.5);
|
||||
assert.ok(u.skill.active);
|
||||
@@ -150,9 +151,9 @@ test('1_04 深巡: fin darts pierce attack@max_target enemies on the line + 1 s
|
||||
});
|
||||
|
||||
test('1_04 深巡 / 1_20 雷蛇 S2 技能策略: both 哨戒铁卫 S2s cast with an enemy in range, no hit needed (issue #4)', () => {
|
||||
// both S2s are offensive (深巡: range up + ATK/ASPD + piercing darts; 雷蛇: ATK +125 % + arts on 3 + stun), and the
|
||||
// mode's rule is "offensive skills activate when an enemy is in their skill range". The 重装 TAKE_DAMAGE row is
|
||||
// documented for skillIndex 0 only — reading it as a wildcard made both wait for a hit.
|
||||
// both S2s are offensive (深巡: range up + ATK/ASPD + piercing darts; 雷蛇: ATK +125 % + arts on 3 + stun); the official
|
||||
// 下半 重装 row (TAKE_DAMAGE for every MANUAL 重装 skill) made both wait for a hit, and the owner's deliberate deviation
|
||||
// from it (DESIGN §21.29, after community feedback) gives them the basic strategy — the data and the kit say DEFAULT.
|
||||
for (const id of ['chess_char_1_04_a', 'chess_char_1_04_b', 'chess_char_1_20_a', 'chess_char_1_20_b']) {
|
||||
const h = run({ units: [{ chessId: id, row: 9, col: 4, carryState: READY }], enemies: [{ key: 'e', pos: [9, 6] }] });
|
||||
const u = h.unit(id);
|
||||
@@ -1116,7 +1117,7 @@ test('2_17 折桠: 生存决心 trembles ground enemies around, ATK/DEF up, hits
|
||||
done(h2);
|
||||
});
|
||||
|
||||
test('2_18 灰毫: 炮术研习 ATK +atk (ground surroundings: ashlok_t_1.atk); TAKE_DAMAGE skill ATK +atk; elite ×1.1 vs blocked', () => {
|
||||
test('2_18 灰毫: 炮术研习 ATK +atk (ground surroundings: ashlok_t_1.atk); skill ATK +atk cast with an enemy in range (DEFAULT, DESIGN §21.29); elite ×1.1 vs blocked', () => {
|
||||
const id = 'chess_char_2_18_a', bb = bbOf(id), t = tal(id);
|
||||
const h = run({ units: [{ chessId: id, row: 10, col: 5 }, { chessId: 'chess_char_2_18_b', row: 10, col: 3 }] });
|
||||
h.step();
|
||||
@@ -1126,7 +1127,11 @@ test('2_18 灰毫: 炮术研习 ATK +atk (ground surroundings: ashlok_t_1.atk);
|
||||
const h2 = run({ defs: { enemies: { e: dummy('e', { atk: 400 }) } }, units: [{ chessId: id, row: 10, col: 5, carryState: READY }], enemies: [{ key: 'e', pos: [10, 5] }] });
|
||||
const u = h2.unit(id);
|
||||
h2.runUntil(() => u.skill.active, 5);
|
||||
assert.equal(h2.hooksOf('skillStart').find((c) => c.unit === u).reason, 'TAKE_DAMAGE');
|
||||
// the owner's deliberate deviation from the 重装 TAKE_DAMAGE row: the basic strategy, no hit needed — she casts before the
|
||||
// enemy she holds has hit her
|
||||
assert.equal(u.skill.rule, 'DEFAULT');
|
||||
assert.equal(h2.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT');
|
||||
assert.ok(!h2.hooksOf('damaged').some((c) => c.target === u), 'not hit before the cast');
|
||||
approx(u.s.atk, u.base.atk * (1 + t['ashlok_t_1.atk'] + bb.atk));
|
||||
done(h2);
|
||||
const idb = 'chess_char_2_18_b', tb = tbOf(idb);
|
||||
|
||||
@@ -364,7 +364,7 @@ test('史尔特尔 S3 黄昏: full heal, max HP +5000, ATK +210 %, range +2, 3 t
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
test('号角 S3: ATK +25 %, BAT −1.2 s, then overload ATK +50 % with HP loss; T1 Defenders ATK +20 %; T2 血战', () => {
|
||||
test('号角 S3: cast with an enemy in range (DEFAULT, DESIGN §21.29); ATK +25 %, BAT −1.2 s, then overload ATK +50 % with HP loss; T1 Defenders ATK +20 %; T2 血战', () => {
|
||||
const h = makeBattle({
|
||||
defs: { enemies: { enemy_dummy: dummy() }, chess: { t_tank: ally('t_tank', { profession: 'TANK', stats: { atk: 100 } }) } },
|
||||
units: [{ chessId: 'chess_char_5_08_a', row: 9, col: 3 }, { chessId: 't_tank', row: 12, col: 3 }],
|
||||
@@ -376,10 +376,12 @@ test('号角 S3: ATK +25 %, BAT −1.2 s, then overload ATK +50 % with HP loss;
|
||||
approx(h.unit('t_tank').s.atk, 100 * (1 + t0.atk), 1e-6, '军事要塞 on another Defender');
|
||||
approx(u.s.atk, u.base.atk * (1 + t0.atk), 1e-6, '… and on herself');
|
||||
u.skill.gainSp(1000);
|
||||
h.run(1);
|
||||
assert.equal(u.skill.activations, 0, '重装 TAKE_DAMAGE: not before a hit');
|
||||
h.b.dealDamage(h.enemies()[0], u, { amount: 10, type: 'phys' });
|
||||
// cast at her next attack with the enemy in range, no hit needed: DEFAULT — the owner's deliberate deviation from the
|
||||
// 重装 TAKE_DAMAGE row (DESIGN §21.29)
|
||||
assert.equal(u.skill.rule, 'DEFAULT');
|
||||
assert.ok(h.runUntil(() => u.skill.active, 5));
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT');
|
||||
assert.equal(u.stats.taken, 0, 'nothing hit her');
|
||||
const start = h.b.time;
|
||||
const total = u.def.skill.duration, ov = bb['horn_s_3[overload_start].damage_duration'];
|
||||
approx(u.s.atk, u.base.atk * (1 + t0.atk + bb.atk));
|
||||
|
||||
Reference in new issue
Block a user