feedback3 WH: 「地面干员」 means a melee-position operator on any tile — 不屈 (both lines), the 战栗维式重锤 proc and 休谟斯 回收利用 no longer read the tile, so a ranged operator on a melee tile gets none of them and 歌蕾蒂娅 on a 高台 gets all (community report 「不屈盟约效果高台干员也错误的吃到了」)

This commit is contained in:
sganggs committed 2026-10-04 20:08:01 +08:00
1 parent d5b945f14b
commit d77148962b
11 files changed
+128 -13

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"yanShip":{"bondId":"yanShip","name":"炎","identifier":1,"isCore":true,"bondType":"SEASON","bondOrder":2,"powerIdList":["yan","sui","lungmen","lee","lgd"],"iconId":"icon_yanShip","activeCount":3,"thresholds":[3,6,9],"maxCount":null,"thresholdTemplate":"count_threshold_upward","countMode":"BOARD","countsHand":false,"countsGoldenOnly":false,"activeType":"BATTLE","isActiveInDeck":false,"noStack":false,"weight":10,"maxInactiveBondCount":-1,"layerMilestones":[],"desc":"【炎】干员攻击力提升(受层数影响)\n<在场6名不同【炎】干员>战斗开始时召唤可自主行动的“炎佑”,其攻击力生命值分别为开战时【炎】干员攻击力生命值总和的30%,“炎佑”同时攻击3个目标,可使用“祛恶之焰”,攻击时附带攻击力一定比例的灼燃损伤且周围敌人受到一定比例的元素脆弱\n<在场9名不同【炎】干员>改为召唤2只“炎佑”,攻击力提升至1.5倍,受到的伤害-90%","descRaw":"【炎】干员<@ba.vup>攻击力</>提升<@autochess.gray>(受层数影响)</>\n<在场<@autochess.dgreen>6</>名不同【炎】干员>战斗开始时召唤可自主行动的<@ba.vup>“炎佑”</>,其攻击力生命值分别为开战时【炎】干员攻击力生命值总和的30%,“炎佑”同时攻击3个目标,可使用<@ba.vup>“祛恶之焰”</>,攻击时附带攻击力一定比例的灼燃损伤且周围敌人受到一定比例的元素脆弱\n<在场<@autochess.dgreen>9</>名不同【炎】干员>改为召唤2只<@ba.vup>“炎佑”</>,攻击力提升至<@ba.vup>1.5</>倍,受到的伤害<@ba.vup>-90%</>","effectId":"bondeffect_yan","effectName":"炎","effectDesc":"【炎】干员攻击力+{0:0%}(受层数影响)\n<在场6名不同【炎】干员>战斗开始时收集【炎】干员部分属性召唤“炎佑”(其攻击力和生命值为【炎】干员攻击力和生命值总和的30%);“炎佑”同时攻击3个目标,攻击附带20%攻击力的灼燃损伤,周围敌人受到20%的元素脆弱,周期性使用“祛恶之焰”\n<在场9名不同【炎】干员>改为召唤2只“炎佑”;“炎佑”攻击力提升至1.5倍,受到的伤害-90%","effectDescRaw":"【炎】干员攻击力<@ba.vup>+{0:0%}</><@ba.acrem>(受层数影响)</>\n<在场<@ba.vup>6</>名不同【炎】干员>战斗开始时收集【炎】干员部分属性召唤“炎佑”(其攻击力和生命值为【炎】干员攻击力和生命值总和的30%);“炎佑”同时攻击3个目标,攻击附带20%<@ba.vup>攻击力</>的<@ba.vup>灼燃损伤</>,周围敌人受到20%的<@ba.vup>元素脆弱</>,周期性使用“祛恶之焰”\n<在场<@ba.vup>9</>名不同【炎】干员>改为召唤2只“炎佑”;“炎佑”攻击力提升至1.5倍,受到的伤害-90%","effectDescParams":[{"index":0,"format":"0%","base":"base_atk","perStack":"atk_per_stack"}],"bb":{"base_atk":0.23,"atk_per_stack":0.009,"power_bond_char_cnt":6,"ex_bond_char_cnt":9,"damage_resistance":0.9,"atk":1.5},"bbStr":{"key":"act1autochess_bond_eff_yan"},"buffs":[{"key":"env_gbuff_new","bb":{"base_atk":0.23,"atk_per_stack":0.009,"power_bond_char_cnt":6,"ex_bond_char_cnt":9,"damage_resistance":0.9,"atk":1.5},"bbStr":{"key":"act1autochess_bond_eff_yan"}}],"baseParams":["base_atk"],"perStackParams":["atk_per_stack"],"members":["chess_char_1_03_a","chess_char_2_04_a","chess_char_3_03_a","chess_char_3_04_a","chess_char_4_15_a","chess_char_4_17_a","chess_char_5_03_a","chess_char_5_12_a","chess_char_5_23_a","chess_char_6_03_a","chess_char_6_15_a"],"visibleMembers":["chess_char_1_03_a","chess_char_2_04_a","chess_char_3_03_a","chess_char_3_04_a","chess_char_4_17_a","chess_char_5_03_a","chess_char_5_12_a","chess_char_5_23_a","chess_char_6_03_a","chess_char_6_15_a"],"spec":{"tiers":[{"need":"3 distinct 炎 on board","effect":"炎 members (+调和 members) ATK x(1 + 0.23 + 0.009*L)."},{"need":"6 distinct","effect":"At battle start spawn 1 \"炎佑\" (enemy_9012_acloon template, allied flying ranged unit, not blockable, moves on its own). ATK = 0.30 x sum(ATK of 炎 operators on board at battle start), maxHP = 0.30 x sum(maxHP of those operators). Template: DEF 200, RES 0, BAT 2.5 s, ASPD 100, attack range radius 2.0 tiles, magic damage, hits up to 3 targets; every hit adds 灼燃损伤 (burn elemental damage) = 20% of its ATK and applies 元素脆弱 (elemental damage taken x1.2) to enemies within 0.8 tiles of the target. Skill \"祛恶之焰\": cooldown 15 s (first cast 15 s after spawn), channels a flame on one enemy in range dealing 60% ATK magic damage per second to it and to enemies within 1.0 tile, for up to 20 s."},{"need":"9 distinct","effect":"Spawn 2 炎佑 instead; each has ATK x1.5 and takes 90% less damage (damage_resistance 0.9)."}],"formulas":{"atkMultiplier":"1 + 0.23 + 0.009*L"},"caps":"none","layerGain":"Economy-driven: 惊蛰 获得时 +shop level (no activation needed); 小满 休整期结束 +1 per operator gained this rouLine truncated
{"yanShip":{"bondId":"yanShip","name":"炎","identifier":1,"isCore":true,"bondType":"SEASON","bondOrder":2,"powerIdList":["yan","sui","lungmen","lee","lgd"],"iconId":"icon_yanShip","activeCount":3,"thresholds":[3,6,9],"maxCount":null,"thresholdTemplate":"count_threshold_upward","countMode":"BOARD","countsHand":false,"countsGoldenOnly":false,"activeType":"BATTLE","isActiveInDeck":false,"noStack":false,"weight":10,"maxInactiveBondCount":-1,"layerMilestones":[],"desc":"【炎】干员攻击力提升(受层数影响)\n<在场6名不同【炎】干员>战斗开始时召唤可自主行动的“炎佑”,其攻击力生命值分别为开战时【炎】干员攻击力生命值总和的30%,“炎佑”同时攻击3个目标,可使用“祛恶之焰”,攻击时附带攻击力一定比例的灼燃损伤且周围敌人受到一定比例的元素脆弱\n<在场9名不同【炎】干员>改为召唤2只“炎佑”,攻击力提升至1.5倍,受到的伤害-90%","descRaw":"【炎】干员<@ba.vup>攻击力</>提升<@autochess.gray>(受层数影响)</>\n<在场<@autochess.dgreen>6</>名不同【炎】干员>战斗开始时召唤可自主行动的<@ba.vup>“炎佑”</>,其攻击力生命值分别为开战时【炎】干员攻击力生命值总和的30%,“炎佑”同时攻击3个目标,可使用<@ba.vup>“祛恶之焰”</>,攻击时附带攻击力一定比例的灼燃损伤且周围敌人受到一定比例的元素脆弱\n<在场<@autochess.dgreen>9</>名不同【炎】干员>改为召唤2只<@ba.vup>“炎佑”</>,攻击力提升至<@ba.vup>1.5</>倍,受到的伤害<@ba.vup>-90%</>","effectId":"bondeffect_yan","effectName":"炎","effectDesc":"【炎】干员攻击力+{0:0%}(受层数影响)\n<在场6名不同【炎】干员>战斗开始时收集【炎】干员部分属性召唤“炎佑”(其攻击力和生命值为【炎】干员攻击力和生命值总和的30%);“炎佑”同时攻击3个目标,攻击附带20%攻击力的灼燃损伤,周围敌人受到20%的元素脆弱,周期性使用“祛恶之焰”\n<在场9名不同【炎】干员>改为召唤2只“炎佑”;“炎佑”攻击力提升至1.5倍,受到的伤害-90%","effectDescRaw":"【炎】干员攻击力<@ba.vup>+{0:0%}</><@ba.acrem>(受层数影响)</>\n<在场<@ba.vup>6</>名不同【炎】干员>战斗开始时收集【炎】干员部分属性召唤“炎佑”(其攻击力和生命值为【炎】干员攻击力和生命值总和的30%);“炎佑”同时攻击3个目标,攻击附带20%<@ba.vup>攻击力</>的<@ba.vup>灼燃损伤</>,周围敌人受到20%的<@ba.vup>元素脆弱</>,周期性使用“祛恶之焰”\n<在场<@ba.vup>9</>名不同【炎】干员>改为召唤2只“炎佑”;“炎佑”攻击力提升至1.5倍,受到的伤害-90%","effectDescParams":[{"index":0,"format":"0%","base":"base_atk","perStack":"atk_per_stack"}],"bb":{"base_atk":0.23,"atk_per_stack":0.009,"power_bond_char_cnt":6,"ex_bond_char_cnt":9,"damage_resistance":0.9,"atk":1.5},"bbStr":{"key":"act1autochess_bond_eff_yan"},"buffs":[{"key":"env_gbuff_new","bb":{"base_atk":0.23,"atk_per_stack":0.009,"power_bond_char_cnt":6,"ex_bond_char_cnt":9,"damage_resistance":0.9,"atk":1.5},"bbStr":{"key":"act1autochess_bond_eff_yan"}}],"baseParams":["base_atk"],"perStackParams":["atk_per_stack"],"members":["chess_char_1_03_a","chess_char_2_04_a","chess_char_3_03_a","chess_char_3_04_a","chess_char_4_15_a","chess_char_4_17_a","chess_char_5_03_a","chess_char_5_12_a","chess_char_5_23_a","chess_char_6_03_a","chess_char_6_15_a"],"visibleMembers":["chess_char_1_03_a","chess_char_2_04_a","chess_char_3_03_a","chess_char_3_04_a","chess_char_4_17_a","chess_char_5_03_a","chess_char_5_12_a","chess_char_5_23_a","chess_char_6_03_a","chess_char_6_15_a"],"spec":{"tiers":[{"need":"3 distinct 炎 on board","effect":"炎 members (+调和 members) ATK x(1 + 0.23 + 0.009*L)."},{"need":"6 distinct","effect":"At battle start spawn 1 \"炎佑\" (enemy_9012_acloon template, allied flying ranged unit, not blockable, moves on its own). ATK = 0.30 x sum(ATK of 炎 operators on board at battle start), maxHP = 0.30 x sum(maxHP of those operators). Template: DEF 200, RES 0, BAT 2.5 s, ASPD 100, attack range radius 2.0 tiles, magic damage, hits up to 3 targets; every hit adds 灼燃损伤 (burn elemental damage) = 20% of its ATK and applies 元素脆弱 (elemental damage taken x1.2) to enemies within 0.8 tiles of the target. Skill \"祛恶之焰\": cooldown 15 s (first cast 15 s after spawn), channels a flame on one enemy in range dealing 60% ATK magic damage per second to it and to enemies within 1.0 tile, for up to 20 s."},{"need":"9 distinct","effect":"Spawn 2 炎佑 instead; each has ATK x1.5 and takes 90% less damage (damage_resistance 0.9)."}],"formulas":{"atkMultiplier":"1 + 0.23 + 0.009*L"},"caps":"none","layerGain":"Economy-driven: 惊蛰 获得时 +shop level (no activation needed); 小满 休整期结束 +1 per operator gained this rouLine truncated
+1 -1
View File
@@ -1537,7 +1537,7 @@ Public GitHub issues filed after the 0.1.1 release (2026-10-03), each triaged ag
- **Now**: build-data sets `placement: 'all'` on a chess whose branch trait without module (`branchTraitText`: the trait candidate's text, else the description) contains 可以放置于远程位 — exactly 崖心, 见行者 and 歌蕾蒂娅, normal and elite (6 records, the only data change; `validateAll` allows it on a MELEE record only; module-only text never counts). `positionClass` returns 'all' for them, so the server takes them on every deployable tile — `g.move`, swaps, a merge's elite (`mergeTile`), the terrain re-check (`_evictIllegal`), invariants and audit —, and the client's `piecePosition` 'ALL' lights the 高台 while dragging; every other melee unit keeps the refusal and its text. The battle keeps position MELEE: on a ground tile they block as before; on a 高台 (`unit.ground` false) they block nothing and attack from there — the sim needed no change (act2 m01, one 萨卡兹枯朽前锋 on the lower lane, 40 s: 歌蕾蒂娅 on (10,4) facing it 2 hits, no block, on (9,5) 12 hits, blocking; 崖心 and 见行者 alike; no errors).
- **Bots** read the record's own position (`basePositionClass`): they still count these units as blockers (`isBlocker`; with the widened class they would not, and on act2 m01 the planner put 歌蕾蒂娅 and 崖心 on the 高台 as non-blockers, leaving 见行者 the only blocker on the road) and plan them on the ground tiles, as in 0.1.1 [ASSUMED]: offered the 高台, the planner still chose road tiles in the cases measured, but the lineup's blocker count assumes they block and a greedy 高台 pick could take a ranged dealer's tile — not benchmarked. `tools/balance.mjs` uses the same blocker test. Tokens are unchanged; the sim's automatic placements (the 突袭 landing tile, summon / device tiles) still treat them as melee, ground tiles only [ASSUMED].
- **Players notice**: 歌蕾蒂娅 can be dragged onto a 高台 (PLAYING §4); there she attacks but blocks nothing. 崖心 and 见行者 get the same rule but are hidden chess this season (`isHidden`, never in the pool, the shop or a 突变细胞 roll), so only 歌蕾蒂娅 is reachable in play; 见行者's elite PUS-X refund (half the DP of a redeploy onto a ranged tile, kits/tier3.js `!unit.ground`) is therefore moot.
- On a 高台 she is not a 地面干员 for the effects that read the tile (`unit.ground`: the 战栗维式重锤 proc, 休谟斯 回收利用, 不屈, the 只攻击地面单位 enemy, 污染秽蚀's 高地 rate), like a ranged operator up there; position-based effects keep her 近战 (精准 tier 2, 诗怀雅's talent) [ASSUMED: the official texts may mean the position class by 地面干员].
- On a 高台 the effects that read the tile (`unit.ground`: the 只攻击地面单位 enemy, 污染秽蚀's 高地 rate) treat her like a ranged operator up there; position-based effects keep her 近战 (精准 tier 2, 诗怀雅's talent) — since 0.1.3 the effects worded 地面干员 too (不屈, the 战栗维式重锤 proc, 休谟斯 回收利用: 地面干员 = the melee position on any tile, see the §23 「地面干员」 subsection; the tile reading 0.1.2 [ASSUMED] here was wrong).
- Normative lines rewritten with it: §3 (placement), §6.6 (the bot); research 03 C2; DATA §2 (`placement`).
- Tests: `test/match/feedback1-placement.test.js` (#32-4: the data rule, cross-checked against character_table when the cache exists; `g.move` onto (10,4) / (11,4) / (12,4) of 战场#01(下半) for the six records; a 重装 refused with its text; ranged unchanged; client highlights = the server's tiles; swaps; the merge's elite on the 高台; the battle input and the sim on / off the 高台; bots), `test/match/board.test.js` (`positionClass` / `basePositionClass`), `test/ui/gameLogic.test.js` (the client mirror).
+2 -2
View File
@@ -8796,11 +8796,11 @@
"tiers": [
{
"need": "2 distinct",
"effect": "When a ground operator is knocked out / retreats / swaps 替身<->本体: with p = min(1, 0.18 + 0.004*L) it is redeployed at once (next deployment has 0 redeploy time and 0 cost). Also consumes a \"复活\" charge if it had one."
"effect": "When a ground operator (地面干员 = melee position, on any tile) is knocked out / retreats / swaps 替身<->本体: with p = min(1, 0.18 + 0.004*L) it is redeployed at once (next deployment has 0 redeploy time and 0 cost). Also consumes a \"复活\" charge if it had one."
},
{
"need": "3 distinct",
"effect": "When a ground operator is knocked out: every operator on the field +5 SP."
"effect": "When a ground (melee-position) operator is knocked out: every operator on the field +5 SP."
}
],
"formulas": {
+2 -2
View File
@@ -963,8 +963,8 @@ Members (9 chess, 9 in current shop pool; by tier in shop: {'1': 1, '2': 1, '3':
**Implementable spec**
- **[2 distinct]** When a ground operator is knocked out / retreats / swaps 替身<->本体: with p = min(1, 0.18 + 0.004*L) it is redeployed at once (next deployment has 0 redeploy time and 0 cost). Also consumes a "复活" charge if it had one.
- **[3 distinct]** When a ground operator is knocked out: every operator on the field +5 SP.
- **[2 distinct]** When a ground operator (地面干员 = melee position, on any tile) is knocked out / retreats / swaps 替身<->本体: with p = min(1, 0.18 + 0.004*L) it is redeployed at once (next deployment has 0 redeploy time and 0 cost). Also consumes a "复活" charge if it had one.
- **[3 distinct]** When a ground (melee-position) operator is knocked out: every operator on the field +5 SP.
- Formulas: `p = min(1, 0.18 + 0.004*L)`; `reaches100%AtL = 205`
- How layers are gained: 雷蛇 休整期结束 +1; 砾 被击倒 +2; 归溟幽灵鲨 +5; 风笛 kills +2; 机变 "风笛的盟誓" +8.
- Bond item (with 变形同构体 grants this bond): 不屈弹射器 `chess_item_2_01_e` (1 gold: 再部署时间-30%,生命值-30%)
+1 -1
View File
@@ -129,7 +129,7 @@ const BY_KEY = {
});
},
// 休谟斯 回收利用
// 休谟斯 回收利用 ("地面干员技能结束时…": a melee-position operator on any tile — support isGroundOp)
act1autochess_band18_buff(battle, ps, p, bandId) {
const sp = num(p.sp);
if (!(sp > 0)) return;
+2
View File
@@ -441,6 +441,8 @@ export function install(battle) {
// next time she leaves the field (knocked out, withdrawn) she is back at once and free (`u.mem.indomFreeDeploy`)
// [ASSUMED: one such deployment at a time, spent by her next deployment whatever brings it]. Tier 2 (+sp SP to every
// operator on the field) stays a knock-out effect: its own line ("地面干员被击倒时使场上所有干员技力+5") was not corrected.
// Both lines take a 地面干员 = a melee-position operator on any tile (support isGroundOp: 歌蕾蒂娅 on a 高台 counts, a
// ranged operator on a melee tile does not — community report 「不屈盟约效果高台干员也错误的吃到了」, 0.1.3).
if (has(ID.indom)) {
const INDOM_EXITS = new Set(['killed', 'retreat', 'merchant']);
const stOf = (u) => { const st = isGroundOp(u) ? byPid[u.ownerId] : null; return st && st.tiers[ID.indom] ? st : null; };
+1 -1
View File
@@ -299,7 +299,7 @@ function hammerAcquire(battle, rt, u) {
const m = hammerMul(battle, rt, u, hs, 'burn');
if (m > 0) battle.dealDamage(u, c.target, { type: 'element', element: 'burn', amount: c.amount * num(p.damage_scale) * m, tags: procTags('hammer_burn') });
});
// 战栗: ground carrier, on attack prob × m ⇒ 战栗 disarmed_duration s
// 战栗: 地面干员 carrier (melee position, any tile — support isGroundOp), on attack prob × m ⇒ 战栗 disarmed_duration s
S.on('attack', (c) => {
if (c.attacker !== u || !isGroundOp(u)) return;
const p = hammerParams(hs, 'tremble');
+1 -1
View File
@@ -40,7 +40,7 @@ function onDefaultSkill(chess) {
* `liftoff` (no ground enemy blocked — Battle._blockerFor; 对地规避 — targeting.js evadesGround: no ground enemy selects
* her, while what selects nobody still lands — 无视无法选择 abilities, direct picks, flying units' blasts, 无来源 damage, the
* ticks of a debuff already on her) and `blockFly` (blocks flyers at the air radius). She stays a ground unit on her tile (PRTS 行动方式 "起飞的
* 干员仍然是地面单位": `unit.ground` unchanged — 隐德来希's 血镰, 地面干员 bonds / items still count her).
* 干员仍然是地面单位": `unit.ground` unchanged — 隐德来希's 血镰 still counts her; 地面干员 bonds / items read her position).
*/
const LIFTOFF_FLAGS = Object.freeze({ blockFly: true, liftoff: true });
/** 蒂比 take-off: the ground enemies she blocked walk on. */
+8 -2
View File
@@ -151,8 +151,14 @@ export function tierOf(u) {
if (u.ownerUnit) return tierOf(u.ownerUnit);
return 1;
}
/** Operator standing on a ground (LOW, not elevated) tile — "地面干员" (engine flag set at deployment). */
export const isGroundOp = (u) => isOp(u) && u.ground === true;
/**
* "地面干员": a melee-position (地面位, deploy class MELEE) operator, whatever tile it stands on — the official vocabulary
* names the deploy class, not the tile (game data: this mode's map card 「可以部署高台干员和地面干员的地面」, 「可将高台干员
* 部署于其上」, 「所有地面干员阻挡数+2」; PRTS corrected 琴柳's 地面干员 to 地面位干员). Read by 不屈, the 战栗维式重锤 proc and
* 休谟斯 回收利用 (community report 「不屈盟约效果高台干员也错误的吃到了」, 0.1.3). The tile (`unit.ground`) stays what the
* enemies' 地面单位 targeting reads.
*/
export const isGroundOp = (u) => isOp(u) && u.def?.position === 'MELEE';
/** Base (normal) chess id of an operator (normal and elite share it). */
export const baseChessId = (u) => String(u?.def?.baseId ?? u?.defId ?? '').replace(/_b$/, '_a');
+11 -2
View File
@@ -412,7 +412,7 @@ test('突袭 #51: the most advanced enemy out of reach → the jump goes to the
checkInvariants(h.b);
});
test('不屈: knocked-out ground operator redeploys (p=1 at high L); tier 2 every operator +5 SP; inactive / elevated → no', () => {
test('不屈: knocked-out 地面干员 (melee position) redeploys (p=1 at high L); tier 2 every operator +5 SP; inactive / ranged → no', () => {
const bb = bondBb('indomShip');
const sk = { spCost: 50, initSp: 0 };
const defs = { chess: { i_g: op('i_g', ['indomShip']), i_o: chessRec({ id: 'i_o', bonds: [], skill: sk }) } };
@@ -441,12 +441,21 @@ test('不屈: knocked-out ground operator redeploys (p=1 at high L); tier 2 ever
close(o1.skill.sp, s1, 'tier 1: no SP');
checkInvariants(t1.b);
// 地面干员 = the melee position, whatever the tile: up on a 高台 it still counts; a ranged operator on a melee tile never
const hi = makeBattle({ defs, units: [{ chessId: 'i_g', row: 10, col: 2 }], bonds: { indomShip: bond(1, 300) } });
hi.step(2);
const hg = hi.unit('i_g');
assert.equal(hg.ground, false, 'elevated tile');
hi.b.dealDamage(null, hg, { amount: 1e9, type: 'true' });
assert.equal(hg.alive, false, 'not a ground operator');
assert.ok(hg.alive && hg.deployed, 'a melee operator on a 高台 is a 地面干员');
const rd = { chess: { i_r: ranged('i_r', ['indomShip']), i_o: chessRec({ id: 'i_o', bonds: [], skill: sk }) } };
const lo = makeBattle({ defs: rd, units: [{ chessId: 'i_r', row: 10, col: 4 }, { chessId: 'i_o', row: 12, col: 6 }], bonds: { indomShip: bond(2, 300, 3) } });
lo.step(2);
const lr = lo.unit('i_r'), lsp = lo.unit('i_o').skill.sp;
assert.equal(lr.ground, true, 'a melee (ground) tile');
lo.b.dealDamage(null, lr, { amount: 1e9, type: 'true' });
assert.equal(lr.alive, false, 'a ranged operator on a melee tile is not a 地面干员');
close(lo.unit('i_o').skill.sp, lsp, 'no tier 2 SP either');
});
test('协防干员: all operators take ×0.8 phys/arts; members deal ×1.2 (elite ×1.4)', () => {
+98
View File
@@ -0,0 +1,98 @@
// test/content/feedback3-ground-ops.test.js — 「地面干员」 is the melee position (地面位), not the tile (0.1.3; community
// report 「不屈盟约效果高台干员也错误的吃到了」). Game data words the deploy class this way (this mode's map card
// 「可以部署高台干员和地面干员的地面」, 「可将高台干员部署于其上」, 「所有地面干员阻挡数+2」); PRTS corrected 琴柳's 地面干员 to
// 地面位干员. The three effects worded 地面干员 — 不屈 (both lines), the 战栗维式重锤 proc and 休谟斯 回收利用 — read
// support isGroundOp = def.position MELEE: 歌蕾蒂娅 on a 高台 counts, a ranged operator on a melee tile does not.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { isGroundOp } from '../../server/sim/content/support/index.js';
const close = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps, `${msg}: ${a} ≠ ${b}`);
const RANGE5 = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]];
const op = (id, o = {}) => chessRec({
id, profession: o.profession ?? 'WARRIOR', bonds: o.bonds ?? [], rangeGrid: RANGE5,
stats: { maxHp: 2000, atk: 500, def: 200, res: 10, aspd: 100, bat: 1, respawnTime: 20, spRecovery: 1, blockCnt: 2 },
skill: { spCost: 60, duration: 5, initSp: 0 },
});
const DUMMY = enemyRec({ key: 'e_d', hp: 1e7, speed: 0 });
function forceRng(h, v) { const orig = h.b.rng; h.b.rng = Object.assign(() => v, orig); }
// act2autochess_m01 (战场#01(下半)): (9,4) a melee tile on the road, (10,4) a 高台
const PROVENCE = 'chess_char_1_07_a'; // 普罗旺斯 狙击 RANGED, 不屈
const GLADIIA = 'chess_char_4_12_a'; // 歌蕾蒂娅 钩索师 MELEE (placement 'all': may stand on a 高台)
const CUORA = 'chess_char_3_16_a'; // 蛇屠箱: takes the tier-2 SP
const INDOM = { indomShip: { count: 3, active: true, tier: 2, layers: 210 } }; // p = min(1, 0.18 + 0.004 × 210) = 1
test('不屈 (real chess): 歌蕾蒂娅 knocked out on a 高台 is back at once with the tier-2 SP; 普罗旺斯 on a melee tile stays down', () => {
const run = (id, row, col) => {
const h = makeBattle({
stageId: 'act2autochess_m01', seed: 4, timeLimit: 60, autoFinish: false, bonds: INDOM,
units: [{ chessId: id, row, col, dir: 'RIGHT' }, { chessId: CUORA, row: 12, col: 6, dir: 'RIGHT' }],
});
h.step(2);
const u = h.unit(id), other = h.unit(CUORA);
const sp0 = other.skill.sp;
h.b.kill(u, null);
const back = u.alive && u.deployed;
const sp = other.skill.sp - sp0;
checkInvariants(h.b);
return { u, back, sp };
};
const g = run(GLADIIA, 10, 4);
assert.equal(g.u.ground, false, '歌蕾蒂娅 stands on the 高台');
assert.equal(g.u.def.position, 'MELEE');
assert.ok(isGroundOp(g.u), 'a 地面干员 up there');
assert.ok(g.back, '不屈 brings her back at once');
close(g.sp, 5, '不屈 tier 2: every operator on the field +5 SP');
const p = run(PROVENCE, 9, 4);
assert.equal(p.u.ground, true, '普罗旺斯 stands on a melee tile');
assert.ok(!isGroundOp(p.u), 'not a 地面干员');
assert.ok(!p.back, '不屈 does not bring a ranged operator back');
close(p.sp, 0, 'no tier-2 SP for her knock-out');
const g2 = run(GLADIIA, 9, 4);
assert.ok(g2.back && g2.sp === 5, '歌蕾蒂娅 on the ground: as before');
});
test('战栗维式重锤: the 战栗 proc takes a melee carrier on a 高台, never a ranged carrier on a melee tile', () => {
const ops = { t_m: op('t_m', { bonds: ['victoriaShip'] }), t_r: op('t_r', { bonds: ['victoriaShip'], profession: 'SNIPER' }) };
const tremble = (id, row, col) => {
const h = makeBattle({
defs: { chess: ops, enemies: { e_d: DUMMY } }, timeLimit: 999, autoFinish: false,
units: [{ chessId: id, row, col, items: ['chess_item_2_03_e_a'] }], enemies: [{ key: 'e_d', pos: [10, 5] }],
});
forceRng(h, 0.05); // under the 10 % chance
h.runUntil(() => h.hooksOf('attack').length >= 1, 5);
assert.ok(h.hooksOf('attack').length >= 1, `${id} attacked`);
const u = h.unit(id);
return { ground: u.ground, hit: !!h.b.enemies[0].findBuff('tremble') };
};
const m = tremble('t_m', 10, 2);
assert.equal(m.ground, false, 'the melee carrier on the 高台');
assert.ok(m.hit, '战栗 from a melee carrier on a 高台');
const r = tremble('t_r', 10, 4);
assert.equal(r.ground, true, 'the ranged carrier on a melee tile');
assert.ok(!r.hit, 'no 战栗 from a ranged carrier');
});
test('休谟斯 回收利用: a melee operator\'s skill end on a 高台 feeds a neighbour 3 SP; a ranged one\'s on a melee tile does not', () => {
const ops = { t_m: op('t_m'), t_r: op('t_r', { profession: 'SNIPER' }), t_n: op('t_n') };
const recycle = (id, row, col, nRow, nCol) => {
const h = makeBattle({
defs: { chess: ops, enemies: { e_d: DUMMY } }, bandId: 'band_humus', timeLimit: 999, autoFinish: false,
units: [{ chessId: id, row, col }, { chessId: 't_n', row: nRow, col: nCol }],
});
h.step(1);
const u = h.unit(id), n = h.unit('t_n');
const n0 = n.skill.sp;
u.skill.activate('test', { free: true });
u.skill.end('test');
return { ground: u.ground, gain: n.skill.sp - n0 };
};
const m = recycle('t_m', 10, 2, 10, 3);
assert.equal(m.ground, false);
close(m.gain, 3, 'melee on the 高台: neighbour +3');
const r = recycle('t_r', 10, 4, 11, 4);
assert.equal(r.ground, true);
close(r.gain, 0, 'ranged on a melee tile: nothing');
});