feedback7 R22-GHSIM: a 速射手 (优先攻击空中单位) blocking a ground enemy shoots a flyer in its range first; every other attacker keeps its blocked enemy first (GitHub #220, the owner's decision of 2026-10-08); golden: roster-026, roster-037 move

The owner's decision (relayed 2026-10-08): only the 速射手 air priority outranks the attacker's own blocked enemy — PRTS
索敌的概念 「阻挡(近战限定)→特殊优先级→…」 — while the user's playtest #6 rule 「阻挡了就一定要能打到」 keeps 'blocked first'
for every other attacker and priority. PR #220 by TsangAsuna (for #205) moved every special priority ahead of the block;
re-implemented for priority 'fly' only (targeting.js sortEnemyTargets: the fly key, then blocked, then the rest as
before) — the trait's priority and a skill's targeting priority 'fly' (新约能天使) alike. Seen (repro): 能天使 on a melee
tile blocking a walker kept shooting it with a flyer in range; now the flyer, and 远牙 (神射手, lowest DEF first) still hits
the enemy it blocks over a softer or a flying one. Docs: SIM.md (blocking), design/engine.md, PLAYING.md 阻挡.

Golden: roster-026 (流星, 速射手) and roster-037 (新约能天使's priority 'fly') now shoot flyers in range while blocking.
This commit is contained in:
sganggs committed 2026-10-08 20:29:45 +08:00
1 parent 895653fc10
commit 4bd154e326
6 files changed
+89 -51

No files matched your search

+30
View File
@@ -66,6 +66,36 @@ test('#1 the user\'s case: ground-only operators never hit a 近地悬浮 enemy
}
});
test('#220 (the owner\'s decision of 2026-10-08): a 速射手 blocking a ground enemy shoots a flyer in its range; a 神射手 keeps hitting the enemy it blocks', () => {
const defs = { enemies: {
e_walker: enemyRec({ key: 'e_walker', hp: 1e8, atk: 0, def: 800, speed: 1 }),
e_soft: enemyRec({ key: 'e_soft', hp: 1e8, atk: 0, def: 0, speed: 0 }),
e_bat: enemyRec({ key: 'e_bat', hp: 1e8, atk: 0, def: 0, speed: 0, motion: 'FLY' }),
} };
const shots = (chessId, other) => {
const h = makeBattle({ seed: 3, autoFinish: false, timeLimit: 120, captureNoisy: true, defs, units: [{ chessId, row: 10, col: 4 }] });
h.step();
const u = h.unit(chessId);
h.b.addBuff(u, { key: 'test:mute', flags: { silence: true } });
const walker = h.spawn('e_walker', { pos: [10, 7], routeIndex: 0, route: { motion: 'WALK', start: [10, 7], end: [10, 0], checkpoints: [] } });
assert.ok(h.runUntil(() => walker.blockedBy === u, 10), `${u.name} on a melee tile blocks the walker`);
const o = h.spawn(other, { pos: [10, 6], routeIndex: 0, route: { motion: other === 'e_bat' ? 'FLY' : 'WALK', start: [10, 6], end: [10, 6], checkpoints: [] } });
h.step();
assert.ok(h.b.enemiesInKeys(u.rangeKeys, u, u.profile).includes(o), `${other} in range`);
const n0 = h.hooksOf('damaged').length;
h.run(4);
const hits = (t) => h.hooksOf('damaged').slice(n0).filter((c) => c.source === u && c.target === t).length;
checkInvariants(h.b);
return { blocked: hits(walker), other: hits(o) };
};
const exu = shots('chess_char_3_01_a', 'e_bat'); // 能天使 (速射手: 优先攻击空中单位)
assert.ok(exu.other > 0 && exu.blocked === 0, `速射手: the flyer over her own blocked enemy (${JSON.stringify(exu)})`);
for (const other of ['e_soft', 'e_bat']) {
const far = shots('chess_char_4_20_a', other); // 远牙 (神射手: 优先攻击防御力最低)
assert.ok(far.blocked > 0 && far.other === 0, `神射手: still the enemy it blocks, not ${other} (${JSON.stringify(far)})`);
}
});
test('#1 ground-only splash spares a hovering enemy next to its ground target (迷迭香 + aftershocks)', () => {
const h = makeBattle({ units: [{ chessId: 'chess_char_6_12_a', row: 10, col: 3 }], seed: 3, autoFinish: false, timeLimit: 120, captureNoisy: true, defs: SYN });
h.step();