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

+30 -15
View File
@@ -452,6 +452,9 @@ const ATTACK_RANGE_CHANGE = /攻击(?:范围|距离)(?:与溅射范围)?(?:扩
* from a ranged hit with nobody to pull). His attack range is his own tile (0-1), so the basic strategy would not see an
* enemy he could pull: his S2 takes SKILL_RANGE on its own 技能范围 (x-1) — the official strategy of a MANUAL skill with a
* 技能范围 when no class row applies, "仅在技能范围内存在敌人(无视其不可选中)时释放技能" — `customRangeGrid` = that range.
* A DEFAULT entry is the basic strategy, so the owner's ACTIVE_RANGE rule applies on top of it (resolveTrigger; the
* owner's decision of 2026-10-05): 深巡 S2, whose running range 3-2 strictly contains her 2-2, casts with an enemy in the
* 3-2 (`rule` ACTIVE_RANGE, `rawRule` still TAKE_DAMAGE).
*/
const TRIGGER_DEVIATIONS = Object.freeze({
chess_char_1_04_a: { skchr_udflow_2: 'DEFAULT' }, // 深巡 S2 行动能力剥夺
@@ -494,6 +497,14 @@ function strictlyContains(a, b) {
return sa.size > sb.size && [...sb].every((k) => sa.has(k));
}
/**
* The rules the owner's ACTIVE_RANGE replaces (resolveTrigger), both of which wait for an enemy in the INITIAL range:
* DEFAULT, the basic strategy (no row, or a DEFAULT deviation of the tables — 深巡 S2, the owner's decision of
* 2026-10-05), and SEARCH, the 解放者 / 阵法术师 / 安洁莉娜 S2·S3 row "不受基础策略影响,在初始攻击范围内存在敌人时释放技能"
* (the owner's decision of 2026-10-05 too).
*/
const ACTIVE_RANGE_OVER = new Set(['DEFAULT', 'SEARCH']);
/**
* Resolve the auto-cast rule of a skill record (PRTS 卫戍协议/帮助 §作战阶段 技能操作 — the official skill strategies;
* DESIGN §5.6):
@@ -511,11 +522,13 @@ function strictlyContains(a, b) {
* unit and skill): `rule` from the table, `rawRule` the official row (a SKILL_RANGE deviation takes the skill's own
* range as `customRangeGrid`);
* - and the owner's rule of 2026-10-05, a deliberate deviation like §21.29 (community report 「有的干员开技能后的攻击范围比
* 平时攻击范围大,但是怪走到平时的攻击范围内才会开技能」): an operator's MANUAL skill left on the basic strategy (DEFAULT,
* no deviation of the table) whose attack range while it runs (activeAttackGrid) strictly contains the operator's own
* range casts as soon as an enemy — a heal skill: an injured ally — is inside that larger range: ACTIVE_RANGE,
* `customRangeGrid` = the running range, `rawRule` the official row. Other rows keep their rule (TAKE_DAMAGE waits for a
* hit, SEARCH reads the initial range by its official row, CUSTOM_RANGE / SP_FULL / … have their own).
* 平时攻击范围大,但是怪走到平时的攻击范围内才会开技能」): an operator's MANUAL skill on the basic strategy (DEFAULT — by
* no row, or by a DEFAULT deviation of the tables: 深巡 S2's 3-2 over her 2-2) or on the SEARCH row (解放者 / 阵法术师 /
* 安洁莉娜: 薄绿 S1, 蜜蜡 S1, 卡涅利安 S3, 玛恩纳 S2, 安洁莉娜 S3) — ACTIVE_RANGE_OVER, both by the owner's decisions
* of 2026-10-05 — whose attack range while it runs (activeAttackGrid) strictly contains the operator's own range casts as
* soon as an enemy — a heal skill: an injured ally — is inside that larger range: ACTIVE_RANGE, `customRangeGrid` = the
* running range, `rawRule` the official row. Other rows keep their rule (TAKE_DAMAGE waits for a hit, CUSTOM_RANGE /
* SKILL_RANGE / SP_FULL / … have their own range or none).
* @param {object} skill record from buildSkill (skillId, skillType, desc, rangeGrid, bb)
* @param {{operator?: boolean, chessId?: string, unitCharId?: string, baseGrid?: number[][]|null}} opts operator = a
* chess (the 技能范围 strategy is written for 干员; summons keep DEFAULT); chessId = the chess's NORMAL id
@@ -540,12 +553,12 @@ function resolveTrigger(ctx, char, charId, skillIdx, skill, { operator = false,
if (!skill.rangeGrid) warn(`trigger deviation ${chessId} ${skill.skillId}: SKILL_RANGE without a 技能范围`);
return { rule: deviation, rawRule, customRangeGrid: skill.rangeGrid ? skill.rangeGrid.map((p) => p.slice()) : null };
}
if (deviation) return { rule: deviation, rawRule, customRangeGrid: null };
const rule = TRIGGER_RENAME[rawRule] || rawRule;
if (rule === 'DEFAULT' && operator && manual && baseGrid?.length) {
const rule = deviation || TRIGGER_RENAME[rawRule] || rawRule;
if (ACTIVE_RANGE_OVER.has(rule) && operator && manual && baseGrid?.length) {
const active = activeAttackGrid(skill, baseGrid);
if (active && strictlyContains(active, baseGrid)) return { rule: 'ACTIVE_RANGE', rawRule, customRangeGrid: active };
}
if (deviation) return { rule: deviation, rawRule, customRangeGrid: null };
let customRangeGrid = null;
if (rawRule === 'CUSTOM_RANGE_SEARCH_ENEMY') {
const rid = ctx.ac.skillRangeDict?.[skill.skillId];
@@ -3437,7 +3450,8 @@ function validateAll(f) {
if (c.placement !== undefined) err(`chess ${c.chessId}: placement is not a data field`);
}
// the deliberate trigger deviations (DESIGN §21.29, §22.10) still override an official TAKE_DAMAGE row, on the normal
// chess and its elite alike
// chess and its elite alike (a DEFAULT one may resolve to the owner's ACTIVE_RANGE on top: 深巡 S2)
const deviationHolds = (rule, trig) => trig.rawRule === 'TAKE_DAMAGE' && (trig.rule === rule || (ACTIVE_RANGE_OVER.has(rule) && trig.rule === 'ACTIVE_RANGE'));
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}`);
@@ -3445,18 +3459,19 @@ function validateAll(f) {
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}`);
else if (!deviationHolds(rule, s.trigger)) err(`trigger deviation ${c.chessId} ${skillId}: ${s.trigger.rawRule} → ${s.trigger.rule}, expected TAKE_DAMAGE → ${rule}`);
else if (rule === 'SKILL_RANGE' && (!s.rangeGrid?.length || JSON.stringify(s.trigger.customRangeGrid) !== JSON.stringify(s.rangeGrid))) err(`trigger deviation ${c.chessId} ${skillId}: SKILL_RANGE needs the skill's own range as customRangeGrid`);
}
}
}
// the owner's ACTIVE_RANGE rule (2026-10-05): a MANUAL skill of the basic strategy whose running range strictly contains
// the record's own range, and no other
// the owner's ACTIVE_RANGE rule (2026-10-05): a MANUAL skill of the basic strategy (DEFAULT by its row or a deviation)
// or of the SEARCH row whose running range strictly contains the record's own range, and no other
for (const c of Object.values(chess)) {
for (const s of c.skills || []) {
if (s.trigger?.rule !== 'ACTIVE_RANGE') continue;
if (s.skillType !== 'MANUAL' || s.trigger.rawRule !== 'DEFAULT' || !c.rangeGrid?.length || !strictlyContains(s.trigger.customRangeGrid || [], c.rangeGrid)) {
err(`chess ${c.chessId} ${s.skillId}: ACTIVE_RANGE needs a MANUAL basic-strategy skill whose customRangeGrid strictly contains the record's range`);
const over = TRIGGER_DEVIATIONS[c.baseId]?.[s.skillId] || s.trigger.rawRule;
if (s.skillType !== 'MANUAL' || !ACTIVE_RANGE_OVER.has(over) || !c.rangeGrid?.length || !strictlyContains(s.trigger.customRangeGrid || [], c.rangeGrid)) {
err(`chess ${c.chessId} ${s.skillId}: ACTIVE_RANGE needs a MANUAL basic-strategy or SEARCH skill whose customRangeGrid strictly contains the record's range`);
}
}
}
@@ -3491,7 +3506,7 @@ function validateAll(f) {
for (const f of forms) {
const s = f.skills.find((x) => x.skillId === skillId);
if (!s) err(`stand-in trigger deviation ${charId}@${statusKey(f.status)}: no skill ${skillId}`);
else if (s.trigger.rawRule !== 'TAKE_DAMAGE' || s.trigger.rule !== rule) err(`stand-in trigger deviation ${charId}@${statusKey(f.status)} ${skillId}: ${s.trigger.rawRule} → ${s.trigger.rule}, expected TAKE_DAMAGE → ${rule}`);
else if (!deviationHolds(rule, s.trigger)) err(`stand-in trigger deviation ${charId}@${statusKey(f.status)} ${skillId}: ${s.trigger.rawRule} → ${s.trigger.rule}, expected TAKE_DAMAGE → ${rule}`);
}
}
}