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

+46 -46
View File
@@ -1612,24 +1612,24 @@
"time": 140,
"ticks": 4200,
"reason": "timeout",
"killed": 18,
"killed": 19,
"total": 73,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 164,
"rngDraws": 169,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":18,"total":73,"perfect":false,"leaks":55,"leaksUncounted":0,"coins":6,"dmg":183879,"bossDmg":0,"heal":1883,"deaths":21,"layerGains":{}}},
"players": {"p1":{"killed":19,"total":73,"perfect":false,"leaks":54,"leaksUncounted":0,"coins":6,"dmg":188151,"bossDmg":0,"heal":1883,"deaths":21,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_3_12_a",0,null,0,0,96,1,7787,4668,1883,19,4],
["p1",2,"op","chess_char_3_16_a",0,null,0,0,6,1,3499,2173,0,12,1],
["p1",3,"op","chess_char_3_18_a",0,null,0,0,99,3,10069,2962,0,12,5],
["p1",1,"op","chess_char_3_12_a",0,null,0,0,97,1,7787,4668,1883,19,4],
["p1",2,"op","chess_char_3_16_a",0,null,1616.62,1,104,1,3518,2729,0,13,1],
["p1",3,"op","chess_char_3_18_a",0,null,0,0,99,4,14899,2962,0,12,9],
["p1",4,"op","chess_char_3_08_a",0,null,0,0,4,4,24642,1594,0,8,1],
["p1",5,"op","chess_char_3_09_a",0,null,1267,1,103,0,13020,1267,0,21,3],
["p1",5,"op","chess_char_3_09_a",0,null,1267,1,103,0,13472,1267,0,22,3],
["p1",6,"op","chess_char_3_10_a",0,null,0,0,87,1,29177,3300,0,17,4],
["p1",7,"op","chess_char_3_11_a",0,null,1316,1,101,1,12760,1316,0,8,1],
["p1",7,"op","chess_char_3_11_a",0,null,1316,1,101,1,11143,1316,0,7,1],
["p1",8,"op","chess_char_3_13_a",0,null,0,0,54,0,4477,4727,0,26,0],
["p1",9,"op","chess_char_3_14_a",0,null,0,0,75,0,4055,2502,0,5,0],
["p1",10,"op","chess_char_3_17_a",0,null,1265.45,1,97,3,44464,3227,0,53,10],
["p1",10,"op","chess_char_3_17_a",0,null,938.6,1,96,2,45052,3553,0,53,10],
["p1",11,"op","chess_char_3_19_a",0,null,0,0,78,1,17767,2878,0,32,0],
["p1",12,"op","chess_char_3_20_a",0,null,0,0,100,1,5659,3654,0,23,1],
["p1",13,"token","token_10028_vigil_wolf",null,null,739.7,0,15,1,4873,969,0,9,null],
@@ -1640,22 +1640,22 @@
],
"enemies": {
"enemy_10042_prtrop": [12,0,12,8673,25590],
"enemy_10042_prtrop_2": [12,0,10,1579,15250],
"enemy_10043_sailor": [39,11,28,18236,171566],
"enemy_10042_prtrop_2": [12,0,10,2136,15720],
"enemy_10043_sailor": [39,12,27,19228,175686],
"enemy_1005_yokai_2": [1,1,0,68,2678],
"enemy_10156_mncrer": [1,1,0,825,6912],
"enemy_1116_liprr_2": [1,1,0,1518,12960],
"enemy_1195_sfyin_2": [1,1,0,147,5357],
"enemy_1196_msfyin_2": [2,2,0,0,10],
"enemy_1251_lysyta_2": [1,1,0,198,5530],
"enemy_1355_mrfly_2": [1,0,1,0,6038],
"enemy_1513_dekght_2": [1,0,1,4110,1400],
"enemy_1355_mrfly_2": [1,0,1,0,5692],
"enemy_1513_dekght_2": [1,0,1,3445,1400],
"enemy_9010_acpupp": [1,0,1,0,1540]
},
"hooks": [1,102,4200,383,383,1447,1501,12,40,94,30,30,0,9243,22,42,73,53,1,43,0,0,0],
"events": {"atk":436,"bounty":3,"deploy":26,"die":41,"dmg":1499,"engage":14,"fx":27,"heal":11,"leak":53,"skill":60,"spawn":90,"status":63},
"fx": {"dp":5,"explode":4,"liberate":1,"phase":1,"revive":4,"shield":2,"sp":1,"strike":1,"summon":2,"telegraph":3,"wolfShadow":1,"wolfShadowLost":2},
"snaps": "1a7d12d1 7955f440 d99919a7 ba142976 2092d384 8dfb7d8e b96cb02d f9c0f4f2 731ec520 3fc5716e da654f48 c224a8bc e3c0b86b 51db902d"
"hooks": [1,103,4200,388,388,1457,1512,12,41,94,34,34,0,9236,19,43,73,52,1,45,0,0,0],
"events": {"atk":441,"bounty":3,"deploy":27,"die":42,"dmg":1510,"engage":14,"fx":33,"heal":11,"leak":52,"skill":68,"spawn":90,"status":58},
"fx": {"dp":9,"explode":4,"liberate":1,"phase":1,"revive":4,"shield":2,"sp":3,"strike":1,"summon":2,"telegraph":3,"wolfShadow":1,"wolfShadowLost":2},
"snaps": "1a7d12d1 7955f440 d99919a7 bdc8b57f 6d3ff9fc 148b6fa6 09898d50 2b3be4b7 5b31a82e 86bf7ebc cb2e4363 3b25093e 5deb7724 6f2e41a0"
},
"roster-027": {
"about": "pass 1 band 4: chess_char_3_06_b chess_char_3_12_b chess_char_3_02_b chess_char_3_03_b chess_char_3_04_b chess_char_3_05_b chess_char_3_21_a chess_char_3_01_b chess_char_3_08_b chess_char_3_09_b chess_char_3_10_b chess_char_3_11_b",
@@ -2250,26 +2250,26 @@
"round": 13,
"seed": 1486935477,
"waveId": "act1autochess_h06",
"time": 216.4,
"ticks": 6492,
"time": 218.23,
"ticks": 6547,
"reason": "cleared",
"killed": 51,
"killed": 47,
"total": 131,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 136,
"rngDraws": 99,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 51,
"killed": 47,
"total": 131,
"perfect": false,
"leaks": 80,
"leaks": 84,
"leaksUncounted": 0,
"coins": 4,
"dmg": 1189101,
"dmg": 1133862,
"bossDmg": 0,
"heal": 36013,
"heal": 36020,
"deaths": 26,
"layerGains": {"sargonShip":6}
}
@@ -2278,15 +2278,15 @@
["p1",1,"op","chess_char_6_06_b",0,"uniequip_003_pepe",0,0,8,1,40812,3361,372,9,3],
["p1",2,"op","chess_char_6_15_b",0,"uniequip_003_qiubai",0,0,6,3,99385,3407,823,19,4],
["p1",3,"op","chess_char_6_07_b",0,"uniequip_003_siege2",0,0,9,0,82237,2661,0,11,4],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,174,0,0,19492,10978,0,4],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,7,287103,1502,0,31,16],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",1663,1,150,0,135318,7690,4364,84,3],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,177,2,23410,4222,166,11,1],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,162,1,63830,6930,1311,115,3],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,173,0,0,18848,10334,0,4],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,7,305406,1502,0,33,17],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",0,0,91,0,85304,7960,4634,55,2],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,176,2,24741,4222,166,12,1],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,161,1,67066,7571,1952,118,2],
["p1",9,"op","chess_char_6_12_b",0,"uniequip_003_rosmon",0,0,64,1,236193,7253,1949,31,9],
["p1",10,"op","chess_char_6_13_b",0,"none",2005,1,171,5,147703,12336,5932,62,6],
["p1",10,"op","chess_char_6_13_b",0,"none",2005,1,171,3,102535,10222,3817,49,5],
["p1",11,"op","chess_char_6_14_b",0,"uniequip_003_lumen",0,0,68,0,0,8834,5745,7,2],
["p1",12,"op","chess_char_6_16_b",0,"none",0,0,173,2,68279,5296,0,20,1],
["p1",12,"op","chess_char_6_16_b",0,"none",1324,1,175,3,86651,8113,2817,28,2],
["p1",13,"token","token_10017_skadi2_dedant",null,null,100,0,17,0,0,0,0,0,null],
["p1",14,"token","token_10030_mlyss_wtrman",null,null,0,0,11,0,1413,4338,2730,14,1],
["-","~1","device","trap_1105_accrate",null,null,100,0,1,0,0,0,0,0,null],
@@ -2296,28 +2296,28 @@
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,54,0,0,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,77,0,39,1608,0,1,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,106,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,114,0,251,3448,1643,8,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,163,0,3054,1497,0,4,1],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,175,0,0,0,0,0,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1804.5,1,176,0,0,0,0,0,1]
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,117,0,1818,2002,680,5,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,162,0,188,1805,0,5,1],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,174,0,0,0,0,0,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1804.5,1,177,0,0,0,0,0,1]
],
"enemies": {
"enemy_10042_prtrop": [1,1,0,698,17199],
"enemy_10118_ymgprc": [1,0,1,5032,115511],
"enemy_1045_hammer_2": [18,4,14,12429,339260],
"enemy_1061_zomshd": [60,12,48,33940,682093],
"enemy_1045_hammer_2": [18,4,14,15105,341760],
"enemy_1061_zomshd": [60,12,48,33152,636194],
"enemy_1071_dftman": [1,1,0,0,8026],
"enemy_1172_dugago": [1,0,1,1324,39158],
"enemy_1172_dugago": [1,0,1,2010,40965],
"enemy_1210_msfden_2": [1,0,1,0,55],
"enemy_1269_nhfly": [45,33,12,24133,24943],
"enemy_1321_wdarft": [1,0,1,0,2107],
"enemy_1501_demonk": [1,0,1,12765,34202],
"enemy_1269_nhfly": [45,29,16,21835,14954],
"enemy_1321_wdarft": [1,0,1,0,0],
"enemy_1501_demonk": [1,0,1,12642,34412],
"enemy_2052_smgia": [1,0,1,0,21689]
},
"hooks": [1,175,6492,504,504,2453,2631,28400,82,166,61,59,39,10872,1110,32,131,80,1,57,1,2,0],
"events": {"atk":605,"bounty":2,"deploy":41,"die":86,"dmg":2534,"engage":15,"fx":82,"heal":20,"layer":1,"leak":80,"skill":120,"spawn":156,"status":656},
"fx": {"airstrike":11,"aoe":8,"burst":2,"copy":5,"explode":29,"garrison":1,"heal":4,"healField":2,"link":2,"revive":1,"split":1,"stone":1,"summon":15},
"snaps": "e7f57cb9 5d2f7fdf a34e9406 8a58323a 69de9406 d21d1321 1a7dd414 1453d4cf 75734598 fe883da1 9cea1cbf d7499dcf 74a67785 3dae277f 03326b45 4d301f69 b4e3db34 2f746db2 a0ce697f 30caa953 92e4bdb1 c3ddaeb8"
"hooks": [1,175,6547,476,476,2445,2593,26429,78,167,60,57,31,10377,1183,31,131,84,1,56,1,2,0],
"events": {"atk":583,"bounty":2,"deploy":41,"die":83,"dmg":2512,"engage":15,"fx":75,"heal":20,"layer":1,"leak":84,"skill":117,"spawn":156,"status":682},
"fx": {"airstrike":7,"aoe":8,"burst":2,"copy":5,"explode":26,"garrison":1,"heal":4,"healField":2,"link":2,"revive":1,"split":1,"stone":1,"summon":15},
"snaps": "e7f57cb9 5d2f7fdf a34e9406 8a58323a 69de9406 d21d1321 1a7dd414 bbd1d05f d2dd7bcb 95474b8f 552bde9f cb4af1a6 e3c30efe 96f7f007 2c71f5ce 666900e2 fcd25fe6 e8e65335 bdbd1c79 5a41718e 0094ace2 d0416309"
},
"roster-038": {
"about": "pass 1 band 7: chess_char_5_08_b chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_5_06_b chess_char_6_17_b chess_char_5_07_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b",
+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();