Files
ark-stronghold-protocol/test/content/strongest-status-overlap.test.js
T
siruimei07 3356b340a7 feedback7 R22-GHSIM: three or more overlapping 同名效果取最高 applications resume strongest first — the middle one no longer drops out when the strongest ends (GitHub #342, PR #348 by @siruimei07); golden: diy-059, diy-122, diy-123, diy-124, diy-133, diy-138 move
The contributor's code (status.js _applyValuedStatus and both test files, applied as written from PR #348), so they are
the author of this commit; the maintainer side added the 同名效果取最高 label back to the JSDoc and a docs/SIM.md line.

buff.data.tail held ONE waiting application (the later-ending one): a third, weaker but longer one replaced a middle one
that was still running, so when the strongest ended the effect jumped to the weakest (#342: 30 % / 25 % / 20 % 减防 —
DEF 1000 → 700 → 800 instead of 750 → 800 → 1000). The tail is now a chain ordered by strength (decreasing) and end
(increasing): a weaker application is inserted in it, a stronger one takes over with the old value and its chain behind
it, an equal refresh trims waiters that end before it, and an expiry resumes the next live waiter with the rest of the
chain. removeStatus / removeBuff still drop the whole chain; two-application data shapes are unchanged.

Golden: six 自选 battles that stack three same-name applications on one unit — diy-059 / diy-122 / diy-123 / diy-124
(庇护 from several operators), diy-133 (Mon3tr's ASPD talent buff talent:monstr:aspd), diy-138 (脆弱) — the middle
application now resumes.
2026-10-08 18:48:26 +08:00

57 lines
2.8 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
test('Dorothy, Pozëmka, and Schwarz DEF reductions fall back to the strongest unexpired application', () => {
const h = makeBattle({
autoFinish: false,
timeLimit: 10,
flags: { dpInit: 0, dpPerSec: 0 },
defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e9, def: 1000, speed: 0 }) } },
units: [
{ uid: 1, diy: { slot: 'chess_char_6_diy1_a', charId: 'char_4048_doroth', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 4 },
{ uid: 2, kind: 'token', tokenId: 'token_10025_doroth_recttp', ownerUid: 1, row: 9, col: 6 },
{ uid: 3, diy: { slot: 'chess_char_6_diy2_a', charId: 'char_4055_bgsnow', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 5 },
{ uid: 4, kind: 'token', tokenId: 'token_10026_bgsnow_subbow', ownerUid: 3, row: 9, col: 5 },
{ uid: 5, diy: { slot: 'chess_char_5_diy1_a', charId: 'char_340_shwaz', skillIndex: 2, uniEquipId: null }, elite: true, row: 9, col: 4 },
],
});
h.step();
// Keep the real kits and projectile travel; prevent extra attacks refreshing their reductions.
for (const unit of h.allies()) unit.atkCd = 100;
const trap = h.unit(2), typewriter = h.unit(4), schwarz = h.unit(5);
const enemy = h.spawn('enemy_dummy', { pos: [9, 6] });
h.step();
assert.equal(enemy.s.def, 700, 'Dorothy S1 applies the initial 30% reduction');
h.run(0.9 - h.b.time);
assert.equal(h.b.forceAttack(typewriter, [enemy]), true);
h.run(1.8 - h.b.time);
schwarz.skill.gainSp(999);
schwarz.atkCd = 0;
h.step();
assert.equal(schwarz.skill.active, true, 'Schwarz S3 guarantees her armor-piercing talent');
schwarz.atkCd = 100;
h.run(2.1 - h.b.time);
const reductions = h.hooksOf('statusApplied').filter((event) => event.target === enemy && event.status === 'defDown');
assert.deepEqual(reductions.map(({ source, value, duration }) => [source.id, value, duration]), [
[trap.id, 0.3, 5],
[typewriter.id, 0.25, 5],
[schwarz.id, 0.2, 5],
]);
for (const [index, expectedTime] of [[0, 0], [1, 1], [2, 2]]) {
assert.ok(Math.abs(reductions[index].t - expectedTime) < 0.1, `reduction ${index + 1} lands near ${expectedTime}s`);
}
assert.equal(enemy.s.def, 700, 'weaker applications do not reduce the active strength');
h.run(5.1 - h.b.time);
assert.equal(enemy.s.def, 750, 'the typewriter reduction survives Dorothy S1 expiring');
h.run(6.1 - h.b.time);
assert.equal(enemy.s.def, 800, 'Schwarz reduction survives the typewriter reduction expiring');
h.run(7.1 - h.b.time);
assert.equal(enemy.s.def, 1000, 'DEF returns to its base value after the final application expires');
assert.deepEqual(h.b.errors, []);
h.invariants();
});