GitHub #316 (warpgenius): "Raid operator doesn't deploy to invisible enemy units, it does in official". The owner's decision of 2026-10-08 from their official play: a 突袭 member considers 隐匿 (unrevealed) ground enemies both as landing targets and for 「若范围内没有敌人」. Until now raidTargets and the poll used canTargetEnemy / enemiesInKeys, which leave 隐匿 out (the bond text only says 敌人 / 地面敌人). content/bonds/addon/battle.js: raidGroundEnemy accepts what canTargetEnemy (ground) accepts, plus a 隐匿 ground enemy that only its 隐匿 keeps out; asleep, untargetable and flying enemies stay excluded (skipEnemy too). raidStealthInRange: a 隐匿 ground enemy on the member's range stops the jump, so a ready member next to one does not hop every poll. The landing rules are unchanged (the target in range from the landing tile; a melee member's blocking tile first). Golden: solo-HARD-1, solo-ABYSS-1, coop2-NORMAL-3, coop4-ABYSS-7, coop2-NORMAL-70-diy — their 突袭 members (斯卡蒂, 瑕光, 宴, 伊内丝) now jump to 隐匿 enemies (潜伏者, 隐形弩手组长, …) or stay because one stands in their range. Tests: test/sim/feedback7-raid-stealth.test.js (4; 2 fail on the parent: the jump to the only landing spot beside a 隐匿 enemy, and staying with one in range while a visible enemy waits elsewhere).
86 lines
4.0 KiB
JavaScript
86 lines
4.0 KiB
JavaScript
// test/sim/feedback7-raid-stealth.test.js — 突袭 and 隐匿 ground enemies: the owner's decision of 2026-10-08 (GitHub #316
|
||
// "Raid operator doesn't deploy to invisible enemy units, it does in official", confirmed by the owner's own play): a 突袭
|
||
// member jumps next to a 隐匿 (unrevealed) ground enemy, and one already in its range counts for 「若范围内没有敌人」 (no
|
||
// hop). Asleep, untargetable and flying enemies stay out, as before (bond text: 「再部署至一名地面敌人周围」).
|
||
// Run: node --test test/sim/feedback7-raid-stealth.test.js
|
||
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||
import { enemyStealthed } from '../../server/sim/targeting.js';
|
||
|
||
const HOME = [12, 3];
|
||
const BONDS = { raidShip: { count: 2, active: true, tier: 1, layers: 0 } };
|
||
/**
|
||
* A field where (10,7) is the only tile a melee member may land on beside the enemy at (10,8): the rest of rows 9–12 is
|
||
* undeployable floor ('f'), its home (12,3) road.
|
||
*/
|
||
const ONE_SPOT = { rows: {
|
||
9: '##Efffffff' + 'S' + 'fffffffS##',
|
||
10: '##hffffrff' + 'f' + 'ffffffff##',
|
||
11: '##hfffffff' + 'f' + 'ffffffff##',
|
||
12: '##hrffffff' + 'S' + 'fffffffS##',
|
||
} };
|
||
|
||
/** `foe` at `pos` (with the buff flags `flags`), and optionally a visible `vis` at `visible`. */
|
||
function field({ pos = [10, 8], flags = null, fly = false, visible = null, range = [[0, 0], [0, 1]] } = {}) {
|
||
const h = makeBattle({
|
||
flat: ONE_SPOT, bonds: BONDS, autoFinish: false, timeLimit: 60, hooks: ['deploy'],
|
||
defs: {
|
||
chess: { raider: chessRec({ id: 'raider', profession: 'WARRIOR', position: 'MELEE', bonds: ['raidShip'], rangeGrid: range, skill: { spCost: 10, initSp: 10 } }) },
|
||
enemies: {
|
||
foe: enemyRec({ key: 'foe', hp: 1e7, speed: 0, atk: 0, motion: fly ? 'FLY' : 'WALK' }),
|
||
vis: enemyRec({ key: 'vis', hp: 1e7, speed: 0, atk: 0 }),
|
||
},
|
||
},
|
||
units: [{ chessId: 'raider', row: HOME[0], col: HOME[1], dir: 'RIGHT' }],
|
||
enemies: [{ key: 'foe', pos }, ...(visible ? [{ key: 'vis', pos: visible }] : [])],
|
||
setup(b) {
|
||
if (flags) b.on('enemySpawn', (c) => { if (/foe$/.test(c.enemy.defId)) b.addBuff(c.enemy, { key: 'test:mark', persist: true, flags }); });
|
||
},
|
||
});
|
||
return h;
|
||
}
|
||
const jumps = (h) => h.hooksOf('deploy').filter((c) => c.unit === h.unit('raider') && !c.initial).length;
|
||
const at = (h) => [h.unit('raider').tileR, h.unit('raider').tileC];
|
||
const foe = (h) => h.b.enemies.find((e) => e.alive && /foe$/.test(e.defId));
|
||
|
||
test('a ready 突袭 member jumps to the only landing spot beside a 隐匿 ground enemy, and does not hop again', () => {
|
||
const h = field({ flags: { stealth: true } });
|
||
h.run(1);
|
||
assert.equal(enemyStealthed(foe(h)), true, 'still 隐匿 (not blocked, not revealed)');
|
||
assert.deepEqual(at(h), [10, 7], 'landed on the one tile beside it');
|
||
assert.equal(jumps(h), 1);
|
||
h.run(3);
|
||
assert.equal(jumps(h), 1, 'the 隐匿 enemy in its range is an enemy in range: no hop');
|
||
assert.deepEqual(at(h), [10, 7]);
|
||
checkInvariants(h.b);
|
||
});
|
||
|
||
test('a visible ground enemy: the same landing (control)', () => {
|
||
const h = field();
|
||
h.run(1);
|
||
assert.deepEqual(at(h), [10, 7]);
|
||
assert.equal(jumps(h), 1);
|
||
checkInvariants(h.b);
|
||
});
|
||
|
||
test('a 隐匿 ground enemy already in its range: 「范围内没有敌人」 is false — it stays, though a visible one waits elsewhere', () => {
|
||
const h = field({ pos: [12, 4], flags: { stealth: true }, visible: [10, 8] });
|
||
h.run(3);
|
||
assert.equal(enemyStealthed(foe(h)), true);
|
||
assert.deepEqual(at(h), HOME);
|
||
assert.equal(jumps(h), 0);
|
||
checkInvariants(h.b);
|
||
});
|
||
|
||
test('asleep or flying 隐匿 enemies are still no destination', () => {
|
||
for (const opts of [{ flags: { stealth: true, sleep: true } }, { flags: { stealth: true }, fly: true }]) {
|
||
const h = field(opts);
|
||
h.run(3);
|
||
assert.deepEqual(at(h), HOME, JSON.stringify(opts));
|
||
assert.equal(jumps(h), 0);
|
||
checkInvariants(h.b);
|
||
}
|
||
});
|