feedback7 R22-GHSIM: 异客 S3's 雷暴 covers range x-1 — the 13-tile diamond — around the tile of the target's centre, not a 1.5-tile circle around its position (GitHub #322, PR #329 by @IceCodeNew, re-implemented); golden: roster-016/018/048, fields boss-boss_1/3/5-pair + hidden-boss_9-pair, matches solo-HARD-1, solo-ABYSS-1, coop2-HARD-4, coop2-ABYSS-10-boosted, coop2-NORMAL-70-diy move

PRTS 异客 S3 备注: 「开启技能时选择技能范围内的目标,以其判定中心所在格为中心生成一个x-1的雷暴区域」; range_table x-1 is the
13-tile diamond (|Δrow| + |Δcol| ≤ 2); x-3 (|技能范围|) stays the search range. The storm's tile is the rounding of the
target's centre (body.js posKey), fixed for its 4 s; a strike picks among the enemies she can select whose body is on
those tiles (enemiesInKeys, as her other range checks); each strike still chains on its own bounce radius. The fx ring is
drawn at radius 2 (visual only). Until now: [ASSUMED] radius 1.5 around the target's exact position — an enemy two tiles
straight off the target's tile was never struck, one at (+1.55, +1.05) off the tile centre was.

Re-implemented from the PR's claim (its golden files were not taken). Golden: every moved scenario fields 异客 casting
S3 — the zone holds other enemies and the random pick draws from another list.
This commit is contained in:
sganggs committed 2026-10-08 19:03:55 +08:00
1 parent 5a50f7b5fa
commit a0cd6826e5
6 files changed
+348 -316

No files matched your search

+26
View File
@@ -1097,6 +1097,32 @@ test('异客: the S3 storm stops when she leaves the field', () => {
assert.equal(h.eventsOf('fx').filter((e) => e[1] === 'lightning').length, n, 'no strike after her retreat');
});
test('异客: the S3 storm is range x-1 — the 13-tile diamond — around the tile of the target\'s centre (PRTS 备注; GitHub #322, PR #329)', () => {
// the storm's target stands off its tile centre at (5.4, 10.4) — tile (10, 5) — and is removed right after the cast, so
// every strike starts on the probe (no chain bounce from the target) when, and only when, the probe is in the zone
const strikes = ([dx, dy]) => {
const h = battle({ units: [{ chessId: 'chess_char_6_05_a', row: 10, col: 3 }], autoFinish: false });
h.step();
const u = h.unit('chess_char_6_05_a');
const tgt = h.spawn('enemy_dummy2', { pos: [10, 5], mods: { hpMul: 2 } });
tgt.x = 5.4; tgt.y = 10.4;
h.step();
assert.ok(u.skill.activate('test', { free: true }));
const storm = h.eventsOf('fx').find((e) => e[1] === 'storm');
assert.deepEqual([storm[2], storm[3]], [5, 10], 'the storm sits on the centre of the target\'s tile');
h.b.dealDamage(null, tgt, { amount: 1e12, type: 'true' });
const p = h.spawn('enemy_dummy', { pos: [10, 5] });
p.x = 5 + dx; p.y = 10 + dy;
h.run(4.2);
checkInvariants(h.b);
return h.dmg.filter((d) => d.src === u.id && d.tgt === p.id && d.tags.includes('storm')).length;
};
// in the diamond (|Δrow| + |Δcol| ≤ 2 by tile): (2, 0) / (0, −2) / (1.95, 0.4) lay outside the old 1.5 circle
for (const off of [[0, 0], [2, 0], [0, -2], [-2, 0], [1.45, 1.45], [1.95, 0.4]]) assert.equal(strikes(off), 8, `(${off}) struck by all 8 strikes`);
// outside it: tile (+2, +1), and tile (+3, 0) — (1.55, 1.05) was inside the old circle around the target's position
for (const off of [[2, 1], [1.55, 1.05], [0, 2.6]]) assert.equal(strikes(off), 0, `(${off}) never struck`);
});
test('蕾缪安: 通缉 needs a continuous 8 s stay in a 拉特兰 range (leaving restarts the count)', () => {
const h = battle({
units: [{ chessId: 'chess_char_6_01_a', row: 12, col: 2 }, { chessId: 'test_lat_a', row: 10, col: 2 }],