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

+15 -10
View File
@@ -121,9 +121,10 @@ test('chess: every non-DIY chess has stats, range, classification and a resolvab
assert.ok(c.assets && c.assets.avatar && c.assets.spine, `${c.chessId}: assets`);
if (c.isGolden) assert.equal(c.module ? c.module.active || c.module.id === null : true, true);
}
// Spot checks against official numbers (隐现 E1 Lv55: HP 1123, ATK 399).
// Spot checks against official numbers (隐现 E1 Lv55: HP 1123, ATK 399 + 攻击力+23 at full potential = 422; cost 15 − 3).
assert.equal(chess.chess_char_1_01_a.stats.maxHp, 1123);
assert.equal(chess.chess_char_1_01_a.stats.atk, 399);
assert.equal(chess.chess_char_1_01_a.stats.atk, 422);
assert.equal(chess.chess_char_1_01_a.stats.cost, 12);
assert.equal(chess.chess_char_1_01_a.targetPriority, 'fly');
});
@@ -577,7 +578,7 @@ test('chess: golden modules[] (+ statsBase/traitBase/talentsBase) compose back t
const m = chess.chess_char_6_11_b;
assert.deepEqual(m.modules.map((x) => [x.uniEquipId, x.typeName, x.isDefault]), [['uniequip_002_mlyss', 'TAC-X', true], ['uniequip_003_mlyss', 'TAC-Y', false]]);
assert.deepEqual(m.modules[1].attr, { maxHp: 170, atk: 28, def: 28 });
assert.deepEqual([m.statsBase.maxHp, m.statsBase.atk, m.statsBase.def], [1703, 467, 111]);
assert.deepEqual([m.statsBase.maxHp, m.statsBase.atk, m.statsBase.def], [1703, 492, 111]); // ATK 467 + 攻击力+25 (full potential)
// module-less goldens: no choices, base = stats
assert.deepEqual(chess.chess_char_5_14_b.modules, []);
assert.deepEqual(chess.chess_char_5_14_b.statsBase, chess.chess_char_5_14_b.stats);
@@ -705,12 +706,13 @@ test('stages: the escaped levels\' maps — one per helper count, never a match
});
test('official spot checks (hard-coded values from the zh_CN client data)', () => {
// at full potential (the owner's decision of 2026-10-07): the client's numbers plus each operator's potential steps
const st = (id) => { const s = chess[id].stats; return [s.maxHp, s.atk, s.def, s.res, s.blockCnt, s.cost]; };
assert.deepEqual(st('chess_char_6_11_b'), [1893, 492, 141, 0, 1, 15]); // 缪尔赛思 精锐 (E2 60 + module Lv3)
assert.deepEqual(st('chess_char_6_17_b'), [3593, 1108, 279, 0, 1, 19]); // 耀骑士临光 精锐
assert.deepEqual(st('chess_char_3_08_a'), [1594, 629, 170, 15, 1, 25]); // 薄绿 (E2 1)
assert.deepEqual(st('chess_char_2_11_a'), [1816, 547, 254, 0, 2, 15]); // 风丸 (E1 60)
assert.deepEqual(st('chess_char_4_07_b'), [2383, 604, 359, 0, 1, 13]); // 风笛 精锐
assert.deepEqual(st('chess_char_6_11_b'), [1893, 517, 141, 0, 1, 13]); // 缪尔赛思 精锐 (E2 60 + module Lv3): 492 + 25 ATK, 15 − 2 cost
assert.deepEqual(st('chess_char_6_17_b'), [3593, 1143, 279, 0, 1, 17]); // 耀骑士临光 精锐: 1108 + 35, 19 − 2
assert.deepEqual(st('chess_char_3_08_a'), [1594, 629, 170, 15, 1, 22]); // 薄绿 (E2 1): 25 − 3
assert.deepEqual(st('chess_char_2_11_a'), [1816, 574, 254, 0, 2, 13]); // 风丸 (E1 60): 547 + 27, 15 − 2
assert.deepEqual(st('chess_char_4_07_b'), [2383, 629, 359, 0, 1, 11]); // 风笛 精锐: 604 + 25, 13 − 2
assert.equal(chess.chess_char_6_11_b.module.level, 3);
assert.deepEqual([chess.chess_char_4_07_b.skill.skillId, chess.chess_char_4_07_b.skill.level, chess.chess_char_4_07_b.skill.spCost], ['skchr_bpipe_2', 7, 5]);
const es = (k) => { const s = enemies[k].stats; return [s.maxHp, s.atk, s.def, s.res]; };
@@ -741,8 +743,11 @@ test('independent re-derivation of every chess and enemy stat from the raw offic
const k0 = P.attributesKeyFrames[0], k1 = P.attributesKeyFrames[P.attributesKeyFrames.length - 1];
const t = (cd.status.charLevel - k0.level) / (k1.level - k0.level || 1);
const f = (key) => k0.data[key] + (k1.data[key] - k0.data[key]) * t;
const exp = { maxHp: f('maxHp'), atk: f('atk'), def: f('def'), res: f('magicResistance'), aspd: f('attackSpeed'), blockCnt: f('blockCnt'), cost: f('cost') };
const map = { max_hp: 'maxHp', atk: 'atk', def: 'def', magic_resistance: 'res', attack_speed: 'aspd', block_cnt: 'blockCnt', cost: 'cost' };
const exp = { maxHp: f('maxHp'), atk: f('atk'), def: f('def'), res: f('magicResistance'), aspd: f('attackSpeed'), blockCnt: f('blockCnt'), cost: f('cost'), respawnTime: f('respawnTime') };
const map = { max_hp: 'maxHp', atk: 'atk', def: 'def', magic_resistance: 'res', attack_speed: 'aspd', block_cnt: 'blockCnt', cost: 'cost', respawn_time: 'respawnTime' };
// full potential (the owner's decision of 2026-10-07): every potentialRanks attribute step (all ADDITION)
const POT = { MAX_HP: 'maxHp', ATK: 'atk', DEF: 'def', MAGIC_RESISTANCE: 'res', ATTACK_SPEED: 'aspd', COST: 'cost', RESPAWN_TIME: 'respawnTime' };
for (const r of CT[shop.charId].potentialRanks || []) for (const m of r.buff?.attributes?.attributeModifiers || []) exp[POT[m.attributeType]] += m.value;
if (cd.status.equipLevel > 0 && shop.defaultUniEquipId) {
const mp = BE[shop.defaultUniEquipId]?.phases.find((p) => p.equipLevel === cd.status.equipLevel);
for (const b of mp?.attributeBlackboard || []) if (map[b.key]) exp[map[b.key]] += b.value;