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
+41
-5
@@ -425,6 +425,23 @@ const TRIGGER_RENAME = { ALWAYS: 'SP_FULL', CUSTOM_RANGE_SEARCH_ENEMY: 'CUSTOM_R
|
||||
*/
|
||||
const ATTACK_RANGE_CHANGE = /攻击(?:范围|距离)(?:与溅射范围)?(?:扩大|改变|缩小|缩短|加长|增加|\+)/;
|
||||
|
||||
/**
|
||||
* A deliberate deviation from the official 技能策略 (DESIGN §21.29): the owner's decision of 2026-10-03 after community
|
||||
* feedback ("反馈的人太多了"; GitHub issue #4, PR #12). The 下半 data's TANK class row (`TANK | | | 0 | TAKE_DAMAGE`,
|
||||
* added with 下半 — 上半 had no TANK row) makes every MANUAL 重装 skill wait for a hit; these six offensive skills cast
|
||||
* with an enemy in range instead — the basic strategy, DEFAULT. Keyed by the NORMAL chess id (the elite follows through
|
||||
* its baseId) and the skill id, never by the skill id alone: 灰毫 S1 skcom_atk_up[3] is a generic skill other chess
|
||||
* carry too (惊蛰, 幽灵鲨, 耶拉, 莫斯提马, 莱恩哈特). The record keeps the official row in `rawRule` (TAKE_DAMAGE) for
|
||||
* traceability; validateAll fails the build when an entry no longer meets a TAKE_DAMAGE row or its skill. 深巡 S1
|
||||
* 侵袭破坏应对 keeps TAKE_DAMAGE.
|
||||
*/
|
||||
const TRIGGER_DEVIATIONS = Object.freeze({
|
||||
chess_char_1_04_a: { skchr_udflow_2: 'DEFAULT' }, // 深巡 S2 行动能力剥夺
|
||||
chess_char_1_20_a: { skchr_liskam_2: 'DEFAULT' }, // 雷蛇 S2 反击电弧
|
||||
chess_char_2_18_a: { 'skcom_atk_up[3]': 'DEFAULT', skchr_ashlok_2: 'DEFAULT' }, // 灰毫 S1 攻击力强化·γ型, S2 专注轰击
|
||||
chess_char_5_08_a: { skchr_horn_2: 'DEFAULT', skchr_horn_3: 'DEFAULT' }, // 号角 S2 暴风号令, S3 终极防线
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve the auto-cast rule of a skill record (PRTS 卫戍协议/帮助 §作战阶段 技能操作 — the official skill strategies;
|
||||
* DESIGN §5.6):
|
||||
@@ -437,11 +454,14 @@ const ATTACK_RANGE_CHANGE = /攻击(?:范围|距离)(?:与溅射范围)?(?:扩
|
||||
* an AUTO skill fires by its own rule (PRTS 古米 S1 备注: "此技能在存在生命值不满的可治疗角色时可触发");
|
||||
* - else, for an operator's MANUAL skill with a 技能范围 (a rangeId that is not an attack-range change): SKILL_RANGE,
|
||||
* "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其不可选中)时释放技能", customRangeGrid = the skill range;
|
||||
* - else DEFAULT (the basic strategy: ready + about to attack / heal).
|
||||
* - else DEFAULT (the basic strategy: ready + about to attack / heal);
|
||||
* - last, the deliberate deviations (TRIGGER_DEVIATIONS, per chess and skill): `rule` from the table, `rawRule` the
|
||||
* official row.
|
||||
* @param {object} skill record from buildSkill (skillId, skillType, desc, rangeGrid)
|
||||
* @param {{operator?: boolean}} opts operator = a chess (the 技能范围 strategy is written for 干员; summons keep DEFAULT)
|
||||
* @param {{operator?: boolean, chessId?: string}} opts operator = a chess (the 技能范围 strategy is written for 干员;
|
||||
* summons keep DEFAULT); chessId = the chess's NORMAL id (TRIGGER_DEVIATIONS key)
|
||||
*/
|
||||
function resolveTrigger(ctx, char, charId, skillIdx, skill, { operator = false } = {}) {
|
||||
function resolveTrigger(ctx, char, charId, skillIdx, skill, { operator = false, chessId = null } = {}) {
|
||||
const rows = Object.values(ctx.ac.skillTriggerDataList || {});
|
||||
const manual = skill.skillType === 'MANUAL';
|
||||
const pick =
|
||||
@@ -453,6 +473,8 @@ function resolveTrigger(ctx, char, charId, skillIdx, skill, { operator = false }
|
||||
return { rule: 'SKILL_RANGE', rawRule: 'DEFAULT', customRangeGrid: skill.rangeGrid.map((p) => p.slice()) };
|
||||
}
|
||||
const rawRule = pick ? pick.skillTriggerType : 'DEFAULT';
|
||||
const deviation = chessId ? TRIGGER_DEVIATIONS[chessId]?.[skill.skillId] : null;
|
||||
if (deviation) return { rule: deviation, rawRule, customRangeGrid: null };
|
||||
const rule = TRIGGER_RENAME[rawRule] || rawRule;
|
||||
let customRangeGrid = null;
|
||||
if (rawRule === 'CUSTOM_RANGE_SEARCH_ENEMY') {
|
||||
@@ -808,7 +830,8 @@ function buildChess(ctx) {
|
||||
Object.assign(rec, traitDefault.classify);
|
||||
|
||||
// Every skill unlocked at the chess status (DESIGN §16), at the chess skill level; the trigger is
|
||||
// resolved per skill (resolveTrigger: charId rows by index, class rows for every MANUAL skill, 技能范围).
|
||||
// resolved per skill (resolveTrigger: charId rows by index, class rows for every MANUAL skill, 技能范围, and the
|
||||
// deliberate deviations of TRIGGER_DEVIATIONS keyed by the normal chess id).
|
||||
const sIdx = shop.defaultSkillIndex ?? 0;
|
||||
const sEntry = char.skills?.[sIdx];
|
||||
const skillLevel = status.skillLevel || 1;
|
||||
@@ -817,7 +840,7 @@ function buildChess(ctx) {
|
||||
if (!se?.skillId || (i !== sIdx && !unlocked(se.unlockCond, phase, level))) return;
|
||||
const s = buildSkill(ctx, se.skillId, skillLevel, null, `chess ${chessId}`);
|
||||
if (!s) return;
|
||||
s.trigger = resolveTrigger(ctx, char, shop.charId, i, s, { operator: true });
|
||||
s.trigger = resolveTrigger(ctx, char, shop.charId, i, s, { operator: true, chessId: baseId });
|
||||
s.index = i;
|
||||
s.overrideTokenKey = se.overrideTokenKey || null;
|
||||
skillRecs.push(s);
|
||||
@@ -3068,6 +3091,19 @@ function validateAll(f) {
|
||||
if (!Array.isArray(c.skills) || c.skills.filter((s) => s.isDefault).length !== 1 || c.skills.find((s) => s.isDefault)?.skillId !== c.skill?.skillId) err(`chess ${c.chessId}: skills[] without exactly one default = skill`);
|
||||
if (c.modules && (c.modules.filter((m) => m.isDefault).length !== (c.module?.active ? 1 : 0) || !c.statsBase || !c.traitBase || !c.talentsBase)) err(`chess ${c.chessId}: inconsistent module choices`);
|
||||
}
|
||||
// the deliberate trigger deviations (DESIGN §21.29) still override an official TAKE_DAMAGE row, on the normal chess
|
||||
// and its elite alike
|
||||
for (const [baseId, skillsOf] of Object.entries(TRIGGER_DEVIATIONS)) {
|
||||
const recs = Object.values(chess).filter((c) => c.baseId === baseId);
|
||||
if (recs.length !== 2) err(`trigger deviation ${baseId}: expected the normal and the elite record, got ${recs.length}`);
|
||||
for (const [skillId, rule] of Object.entries(skillsOf)) {
|
||||
for (const c of recs) {
|
||||
const s = (c.skills || []).find((x) => x.skillId === skillId);
|
||||
if (!s) err(`trigger deviation ${c.chessId}: no skill ${skillId}`);
|
||||
else if (s.trigger.rawRule !== 'TAKE_DAMAGE' || s.trigger.rule !== rule) err(`trigger deviation ${c.chessId} ${skillId}: ${s.trigger.rawRule} → ${s.trigger.rule}, expected TAKE_DAMAGE → ${rule}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const b of Object.values(bonds)) {
|
||||
for (const m of b.members) if (!chess[m]) err(`bond ${b.bondId}: member ${m} missing`);
|
||||
if (!b.thresholds.length) err(`bond ${b.bondId}: no thresholds`);
|
||||
|
||||
Reference in new issue
Block a user