feedback1f WV: a knocked-out operator keeps its tile; 突袭 never lands on it

Player report F5 after 0.1.0: "突袭盟约的干员会移动到濒死干员的尸体上".

Real. A knocked-out operator left the occupancy map at once and only its
home tile stayed reserved. A 突袭 member that fell after its jump lay on the
landing tile, which nothing reserved, so the next member's jump (same target,
same scoring) landed on it; and a member's timed redeploy came back on its home
where a teammate that had jumped there lay. Real matches (six real 突袭
members, 战场#07 / #08, R6 / R9 / R12, 96 battles): 6 placements on a body
(3 jumps, 3 home redeploys) before, 0 of 526 jumps after.

Official (PRTS 卫戍协议/帮助, 作战阶段 / 单位部署):
- "干员退场后…原地留下一个“倒地干员”…满足再部署条件时,移除场上的该倒地干员
  并自动部署至该位置"
- "倒地干员所在地块视为可部署,但所有我方单位在此处的部署行为将被阻止"
- "若干员被击倒的位置为其他干员或召唤物的初始位置,则在被击倒后,尝试返回其
  自身的初始位置"

Now (sim/Battle.js):
- A knocked-out operator lies on `unit.body`: the tile it fell on, or its
  own home when it fell on another board piece's home and its home is free
  (one attempt at the knock-out, assumed). x / y / tileR / tileC keep where
  it fell, so the kill / death handlers still act there.
- Every redeploy comes back there (restTile): the timed one, 不屈's and
  阿戈尔's. A retreated operator still comes back on its home.
- downOn(r, c): _deploy (redeploys, the 突袭 landing, summons), spawnDevice
  and relocate refuse the tile for every ally of the field; isReservedTile
  reports it, so every automatic picker skips it (突袭 raidTile, tactical
  points, summon / device pickers, 维娜's lions). The kit helpers that kept
  their own home-tile loop (tier1 summonTileFree, tier3 / tier4 freeTile) go
  through isReservedTile.
- b.snap `down` entries gain the body tile: [id, respawnAt, respawnTime,
  state, row, col]. DOWN_STATE.WAIT_TILE becomes a safeguard only.

Tests: test/sim/feedback1f-downed-tile.test.js (real Match / PlayerState /
BattleSpec runs with real 突袭 chess, the 突袭 jump, the engine refusals,
the return home); downelem and bonds_addon follow the official rule.
This commit is contained in:
sganggs committed 2026-10-03 07:16:24 +08:00
1 parent 60ed4fdab9
commit 5a3d562b75
12 files changed
+374 -71

No files matched your search

+12 -8
View File
@@ -25,13 +25,16 @@
// 突袭 raidShip member idle ≥ no_attack_duration s (or skill ready) with no enemy in range → "保留技力立即再部署"
// next to the most advanced ground enemy, on a free tile its position may be deployed on
// (grid.canStand: never the 深水区 — player report #3 after 0.1.0, members dropped into 战场#08's
// pool after an enemy wading in it): a real redeployment (retreat + free redeploy on the
// landing tile, full HP, `deploy` fires — 部署时 traits such as 史尔特尔, 突袭手雷, 卡西米尔, 叙拉古)
// with its SP / charges kept (engine redeploy tile + keepSp); ATK/HP +(base + per·L)
// until it leaves the field; later redeploys use its board tile again;
// pool after an enemy wading in it; Battle.isReservedTile: never a tile a knocked-out operator
// lies on — player report F5, members landed on a fallen teammate): a real redeployment (retreat +
// free redeploy on the landing tile, full HP, `deploy` fires — 部署时 traits such as 史尔特尔,
// 突袭手雷, 卡西米尔, 叙拉古) with its SP / charges kept (engine redeploy tile + keepSp); ATK/HP
// +(base + per·L) until it leaves the field; knocked out after a jump it lies where it fell and
// comes back there (the engine's rest tile, Battle._layBody — PRTS 卫戍协议/帮助 "原地留下一个
// “倒地干员”…自动部署至该位置"; its own home when it fell on another board piece's home);
// L ≥ power_bond_stack_cnt: every operator ASPD +power_attack_speed
// 不屈 indomShip ground operator knocked out → p = min(1, base + per·L) immediate free redeploy; tier 2: every
// operator on the field +sp SP
// 不屈 indomShip ground operator knocked out → p = min(1, base + per·L) immediate free redeploy where it lies
// (the engine's rest tile); tier 2: every operator on the field +sp SP
// 协防 emptyShip all operators phys/arts taken ×(1 − damage_resistance); members dealt ×damage_scale_normal
// (elite ×damage_scale_extra)
// 独行 soloShip the member(s) ATK +atk, HP +max_hp, +sp SP on every deploy
@@ -289,8 +292,9 @@ function raidPoll(battle, st) {
/**
* "保留技力立即再部署": retreat the unit and redeploy it for free on (r, c) with its SP / charges kept (engine redeploy
* `tile` + `keepSp`: restored before the `deploy` handlers run; the board tile stays the home of later redeploys).
* Should the landing fail, the unit goes straight back to its home tile (never stranded off the field).
* `tile` + `keepSp`: restored before the `deploy` handlers run; the board tile stays its home). Should the landing fail
* (raidTile skips taken, reserved and body tiles, so only content refusing it), the unit goes straight back to its home
* tile (never stranded off the field).
* Returns 'raid' | 'home' | false.
*/
function raidRedeploy(battle, u, r, c) {
+5 -4
View File
@@ -400,9 +400,10 @@ function installEgir(battle, pid, bb, members) {
battle.on('battleStart', () => devour(battle, pid, bb, members), { once: true });
if (!reached(battle, pid, 'egirShip', bb.power_bond_char_cnt)) return;
// 5: "前3名【阿戈尔】干员首次被击倒时立刻复活" — PRTS: the knocked-out unit's next deployment has 0 redeploy time and
// 0 cost, i.e. it IS knocked out (被击倒 triggers, 克莱门莎, 幽灵鲨 … fire) and redeploys at once on the tile it was
// knocked out on (engine redeploy `tile`: a raid-relocated member comes back where it fell, later redeploys use its
// board tile) with full HP, SP reset and `deploy` effects (卡西米尔 / 叙拉古). Death priority 11: before 不屈 (10),
// 0 cost, i.e. it IS knocked out (被击倒 triggers, 克莱门莎, 幽灵鲨 … fire) and redeploys at once where it lies (the
// engine's rest tile, Battle._layBody: the tile it was knocked out on — a raid-relocated member comes back where it
// fell —, or its own home when it fell on another board piece's home; PRTS 卫戍协议/帮助 §作战阶段 单位部署) with full
// HP, SP reset and `deploy` effects (卡西米尔 / 叙拉古). Death priority 11: before 不屈 (10),
// whose redeploy "also consumes a 复活 charge" — with this order the charge is always the one used, same outcome.
const memberSet = new Set(members);
const max = Math.max(0, Math.floor(num(bb.max_free_respawn_cnt, 0)));
@@ -412,7 +413,7 @@ function installEgir(battle, pid, bb, members) {
if (c.reason !== 'killed' || !memberSet.has(u) || st.knocked.has(u)) return;
st.knocked.add(u);
if (st.revives >= max || u.alive || u.removed) return;
if (!battle.redeploy(u, { free: true, tile: [u.tileR, u.tileC] }) && !battle.redeploy(u, { free: true })) return;
if (!battle.redeploy(u, { free: true })) return;
st.revives++;
S.fxOn(battle, 'revive', u, 'bond:egirShip', 'respawn', { n: st.revives });
}, { priority: 11 });