feedback6: merge R21P — every operator at full potential (潜能 6; GitHub #252, PR #255 by @SpiritedAwayCN, the owner's decision of 2026-10-07)

- tools/build-data.mjs applies every potentialRanks attribute step (COST, RESPAWN_TIME, ATK, MAX_HP, DEF,
  MAGIC_RESISTANCE, ATTACK_SPEED) to the chess, elites, 补位 and 自选 units and picks talent / trait / module-talent
  candidates up to requiredPotentialRank 5; summons take the owner's potential for their talents, not its stat steps.
  data/chess.json, backups.json, tokens.json and the en / ja / ko / zh-TW data translations rebuilt; op-wang.js reads
  her stock cap (8 at full potential). 刺玫 435 ATK / cost 15, 砾 16 s, 至简 70 s.
- No conflicts outside the golden files (regenerated). Golden vs HEAD: roster 49, bonds 46, fields 22, matches 18,
  diy 133 (standins and diy-012 / diy-135…138 unchanged: no potential ranks or never hit). Union check against the
  branch and the merge base: every scenario only the branch moved equals the branch, except roster-048 and
  solo-ABYSS-2: at full potential a 频次 器物 is reached there (roster-048: one more enemy dies instead of leaking, 86 → 87
  units), so R21H's hit count (data × hpMul ÷ supplyHpMul) applies where it did not before — with only that rule put back
  (archetypes.js times as at the merge base) both equal the branch exactly.
- Tests: broad node --test 5147 / 5147; lint 0 errors.
This commit is contained in:
sganggs committed 2026-10-07 18:48:38 +08:00
commit b05845f507
178 files changed
+7667 -7317

No files matched your search

+10 -8
View File
@@ -210,7 +210,8 @@ test('纸偶: appear burst = its ATK × damage_scale arts on the 8 surrounding t
test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), fatal sheds a shadow, DEF ignore vs blocked, 战术点形态 after the last one', REAL, () => {
let wolfHits = 0;
const h = makeBattle({
defs: { enemies: { enemy_walker: walker({ def: 200, atk: 0 }) } },
// DEF 250: above the pack's 200 DEF ignore (its talent at 伺夜's full potential), so a bite shows the partial ignore
defs: { enemies: { enemy_walker: walker({ def: 250, atk: 0 }) } },
kits: { chess_char_3_19_a: genericNoSkill },
// the wolf is listed before its owner (higher row): it must still be linked to 伺夜 (owner-level variant)
units: [{ kind: 'token', tokenId: TOKEN_IDS.wolfPack, row: 9, col: 6, uid: 1, ownerUid: 2 }, { chessId: 'chess_char_3_19_b', row: 10, col: 4, uid: 2 }],
@@ -228,11 +229,12 @@ test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits),
assert.equal(wolf.s.blockCnt, 2);
const e = h.enemy('enemy_walker');
assert.ok(h.runUntil(() => e.blockedBy === wolf && wolfHits >= 2, 30), 'blocks and bites');
// DEF ignore: every bite does ATK − (200 − 175)
// DEF ignore: every bite does ATK − (250 − 200)
assert.equal(tokDef(TOKEN_IDS.wolfPack, 'chess_char_3_19_b').talents[1].bb.def_penetrate_fixed, 200, 'the pack ignores 200 DEF (175 + the potential step)');
let dealt = null;
h.b.on('damaged', (c) => { if (c.source === wolf && c.type === 'phys') dealt = c.amount; });
h.runUntil(() => dealt != null, 5);
approx(dealt, wolf.s.atk - (200 - 175), 1e-9, 'def pen');
approx(dealt, wolf.s.atk - (250 - 200), 1e-9, 'def pen');
h.runUntil(() => wolfShadows(wolf) === 3, 30);
assert.equal(wolf.s.blockCnt, 3);
assert.equal(wolf.profile.hitsFn(h.b, wolf), 3);
@@ -927,7 +929,7 @@ test('every token variant: a spawn with that owner takes the owner-level stats (
assert.ok(n >= 38, `variants checked: ${n}`);
});
test('elite numbers: 沙之碑 230 %/1.5 s, 香槟炸弹 170 %, 纸偶 its ATK × 2.7, “耀阳” 100 % + ×1.15 vs blocked, 医疗探机 ATK 114', REAL, () => {
test('elite numbers: 沙之碑 230 %/1.5 s, 香槟炸弹 170 %, 纸偶 its ATK × 2.75 (full potential), “耀阳” 100 % + ×1.15 vs blocked, 医疗探机 ATK 114', REAL, () => {
const burstOf = (ownerId, tokenId, r, c, ePos) => {
const base = ownerId.replace(/_[ab]$/, '_a');
const h = makeBattle({ defs: { enemies: { enemy_dummy: dummy() } }, kits: { [base]: genericNoSkill }, units: [{ chessId: ownerId, row: 12, col: 2 }], enemies: [{ key: 'enemy_dummy', pos: ePos }], autoFinish: false, timeLimit: 10 });
@@ -951,7 +953,7 @@ test('elite numbers: 沙之碑 230 %/1.5 s, 香槟炸弹 170 %, 纸偶 its ATK
{
const { t, e } = burstOf('chess_char_2_11_b', TOKEN_IDS.paperDoll, 10, 6, [11, 7]);
assert.equal(t.base.atk, 728);
approx(1e7 - e.hp, 728 * 2.7, 1e-9, '纸偶 elite');
approx(1e7 - e.hp, 728 * 2.75, 1e-9, '纸偶 elite (2.7 + the potential step of 风丸)');
}
{
const { o, e } = burstOf('chess_char_6_17_b', TOKEN_IDS.radiantSword, 10, 6, [10, 7]);
@@ -1228,7 +1230,7 @@ test('tactical point: without a board piece the 援军 (狼群) stands on an ene
checkInvariants(h.b);
});
test('狼群 (generic 伺夜): 伺夜\'s own attacks on pack-blocked enemies ignore 175 DEF and get the S3 bonus; the pack leaves with 伺夜', REAL, () => {
test('狼群 (generic 伺夜): 伺夜\'s own attacks on pack-blocked enemies ignore 200 DEF (full potential) and get the S3 bonus; the pack leaves with 伺夜', REAL, () => {
const kit = () => ({ generic: true, talents: [], skill: { kind: 'duration', duration: 60, trigger: 'SP_FULL', spCost: 1, initSp: 1 } });
const own = [], bonus = [];
// RES 100: the 弱点伤害 garrison keeps the attacks physical (arts would do 5 %)
@@ -1244,8 +1246,8 @@ test('狼群 (generic 伺夜): 伺夜\'s own attacks on pack-blocked enemies ign
assert.ok(h.runUntil(() => own.some((x) => x.blocked) && bonus.some((x) => x.src === 'chess_char_3_19_a'), 30), 'owner hits a pack-blocked enemy');
const hit = own.find((x) => x.blocked);
assert.equal(hit.type, 'phys');
// tactician trait ×1.5 vs enemies its 援军 blocks; DEF 300 − 175
approx(hit.amount, vigil.s.atk * 1.5 - (300 - 175), 1e-6, 'DEF ignore on 伺夜\'s attack');
// tactician trait ×1.5 vs enemies its 援军 blocks; DEF 300 − 200
approx(hit.amount, vigil.s.atk * 1.5 - (300 - 200), 1e-6, 'DEF ignore on 伺夜\'s attack');
approx(bonus.find((x) => x.src === 'chess_char_3_19_a').amount, vigil.s.atk * 0.2 * 0.05, 1e-6, 'S3 bonus: 20 % ATK arts vs RES 100 (5 % floor)');
assert.ok(bonus.some((x) => x.src === TOKEN_IDS.wolfPack), 'the pack\'s bites carry the bonus too');
// the 援军 leaves with its tactician (no respawn afterwards)