feedback6 R21E: an operator's 凋亡 burst drains the official 技力 — stored charges and the ready charge included (Skill.setSpTotal(spTotal − 1) each second; PRTS 技能 可充能 「当持有者的技力流失时,充能次数也会实时降低」, PRTS 元素 「每秒损失1点技力」): the drain took only the SP towards the next charge, so a full skill kept every charge and a one-charge skill stayed ready through the 15 s; a running timed skill is left alone as before; PR #262 (#261 its closed duplicate), re-implemented; golden: 5 moved, each by a burst on an operator with SP stored (假想敌:管's 凋亡 strikes, the 萨卡兹王庭军's 凋亡 on hit) — fields hidden-boss_10-solo (泥岩 loses its stored charge: casts 1 → 0, the move PR #262 reported; the team's heals and the snapshots after it), boss-boss_3-solo / hidden-boss_10-pair and diy-058 (later snapshots and event counts only, the same results), diy-059 (黍 forms against the 萨卡兹王庭军 waves: fewer casts — 11 → 5, 6 → 2, 9 → 5, 6 → 1 — the battle goes otherwise, kills 39 → 47, leaks 57 → 49)

This commit is contained in:
sganggs committed 2026-10-07 17:58:45 +08:00
1 parent c623550f29
commit c225559d4d
5 files changed
+109 -40

No files matched your search

@@ -0,0 +1,65 @@
// PR #262 (#261 its closed duplicate): an operator's 凋亡 burst — PRTS 元素 "立即获得等时长的阻回、静默,该阻回、静默持续期间
// 每秒损失1点技力并受到100点无来源法术持续伤害" — drains the official 技力, stored charges included: PRTS 技能 可充能 "当持有者
// 的技力流失时,充能次数也会实时降低". The drain took only the SP towards the next charge, so a full skill (sp = cost with
// every charge stored) kept all its charges — a one-charge skill stayed ready through the whole burst. An active timed
// skill is left alone (its SP was spent at the cast; Skill.setSpTotal leaves it too).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
import { hasGeneratedData } from '../../server/sim/simdata.js';
const REAL = { skip: !hasGeneratedData() };
const SNOW = 'chess_char_3_11_b'; // 精锐 雪猎: S2 风雪连弩, MANUAL, 可充能2次, 14 SP per charge
function arena(units, defs = {}) {
const h = makeBattle({ defs, units, autoFinish: false, timeLimit: 120, hooks: ['spGain', 'damaged'] });
h.step();
return h;
}
const burst = (h, u) => {
h.b.dealDamage(null, u, { type: 'element', element: 'apoptosis', amount: u.gaugeMax });
assert.ok(u.findBuff('apoptosisBurst'), '凋亡 bursts');
};
test('PR #262: 凋亡 drains a charge skill\'s stored charges — 精锐 雪猎 with both charges loses one at the first second and is empty after 15', REAL, () => {
const h = arena([{ chessId: SNOW, row: 10, col: 4 }]);
const u = h.unit(SNOW), sk = u.skill;
assert.equal(sk.maxCharges, 2);
const cost = sk.spCost;
sk.setSpTotal(2 * cost);
assert.deepEqual([sk.charges, sk.sp, sk.spTotal], [2, cost, 2 * cost], 'both charges stored');
burst(h, u);
h.run(1.01);
assert.deepEqual([sk.charges, sk.sp, sk.spTotal], [1, cost - 1, 2 * cost - 1], 'one second: −1 技力, the second charge goes');
assert.equal(sk.ready, true, 'one charge left');
h.run(14);
// (the burst ends with its 15th second: the time SP of the ticks since then is back)
assert.equal(sk.charges, 0, '15 seconds: −15 技力 — the last charge gone too');
assert.ok(Math.abs(sk.spTotal - (2 * cost - 15)) < 0.1, `${sk.spTotal} ≈ ${2 * cost - 15}`);
assert.equal(sk.ready, false);
assert.equal(sk.activations, 0, '静默: no cast through the burst');
assert.ok(!u.findBuff('apoptosisBurst'), 'the burst is over');
checkInvariants(h.b);
});
test('PR #262: a full one-charge skill is no longer ready after the first second of 凋亡; an active timed skill keeps its state', () => {
const op = chessRec({ id: 't_apo', stats: { maxHp: 1e5, spRecovery: 0 }, skill: { spCost: 20, initSp: 0, duration: 30, durationType: 'NONE' } });
const h = arena([{ chessId: 't_apo', row: 10, col: 4, uid: 1 }, { chessId: 't_apo', row: 10, col: 6, uid: 2 }], { chess: { t_apo: op } });
const a = h.unit(1), b = h.unit(2);
a.skill.setSpTotal(20);
assert.equal(a.skill.ready, true);
burst(h, a);
h.run(1.01);
assert.deepEqual([a.skill.charges, a.skill.sp, a.skill.ready], [0, 19, false], 'the ready charge is part of the 技力 it loses');
h.run(14);
assert.equal(a.skill.spTotal, 5, '−15 技力 in all');
assert.equal(h.hooksOf('spGain').length, 0, 'the drain is no SP gain (no spGain hook)');
// a running timed skill: its SP was spent at the cast — nothing to drain, and it runs on (the burst silences new casts only)
b.skill.setSpTotal(20);
assert.ok(b.skill.activate('test'));
const before = [b.skill.active, b.skill.charges, b.skill.sp];
burst(h, b);
h.run(3);
assert.deepEqual([b.skill.active, b.skill.charges, b.skill.sp], before);
checkInvariants(h.b);
});