Files
ark-stronghold-protocol/test/sim/feedback5-raid-revive.test.js
T
sganggs 9ca52ba798 feedback5: merge CB1 — community reports (bonds / strategies / items): a pair of colds on an enemy becomes the freeze and leaves no cold (the 谢拉格 wind no longer freezes for good), a 复活 (埃芒加德's 命结之秘, M3茧甲) is a knock-out and an immediate free redeploy where the operator lies (砾's and 瑕光's 被击倒时 / 部署时 layers fire), 佩佩's special refresh is paid, the result card hides the layers of 调和 / 协防干员 / 独行 / 绝技, the 炎佑 keeps off 外勤医疗's slot, 凯瑟琳's 定向投放 offers every shop item; 烛煌's 绝处重燃 before the hammer's lock and the 寻呼模块 / 简易通讯机 tier cap kept as they are (the owner's decisions of 2026-10-07); §25.15 → §25.21 (§25.15 is the shortcuts'); golden: vs feedback5 CB1's own rules — roster-005/009/010/012/014/015/016/018/026/029/031/033/036/040/042/046/047/049, bond-egirShip-high, bond-indomShip-low, bond-kjeragShip-high, bond-maniShip-high, bond-raidShip-low, bond-steadShip-high, bond-suntShip-low, boss-boss_2/3/4/6-pair, hidden-boss_8/9/10-pair, standin-07, matches solo-FUNNY-1, solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7; vs CB1 what feedback5 changed since its base (CB2, CB3, CB4, CB5) plus roster-014
Conflicts resolved by hand:
- server/sim/content/items/battle.js: CB3 had extended `revivedInPlace` (an in-place 复活 restarts a deploy-timed
  skill, item 44); CB1 replaced the in-place revive by PRTS's form — `reviveNow`, a `death` hook that redeploys the
  operator at once, free, where it lies — and removed `revivedInPlace`. CB1's side is kept: its redeploy resets the
  skill, so an `activateOnDeploy` skill starts anew by itself; CB3's tests (test/sim/feedback5-raid-revive.test.js:
  the jump at once after M3茧甲 / 埃芒加德, the window back to full, 砾's fresh barrier) pass on it unchanged. Their
  header, bonds_core's comment, DESIGN §25.17.5 say so.
- docs/SIM.md, docs/design/engine.md: CB1's `fatal` rows (the 复活 are `death` hooks now); the `death` rows also say a
  deploy-timed skill starts anew (§25.17.5).
- data/choices.json: no conflict; `node tools/build-data.mjs --offline` on the merged tree rewrites all 15 data files
  byte-identically, and the JSON compare with feedback5 shows only pools.pool_equip_kathe.maxTier removed (CB1).
- docs/history/0.2.0.md: CB1's section renumbered §25.21 (its §25.15.1–10 and two self-citations; no citation of it
  elsewhere), placed last; the owner's decisions of 2026-10-07 on items 5 and 45 recorded there, in the intro and in the
  two pin tests' headers (feedback5-blaze-hammer, feedback5-cb1-pins).

Golden: solo-FUNNY-1 (outside CB1's list) has 6 埃芒加德 revives and 85 freezes on feedback5's path (instrumented).
roster-014 differs from CB1's own digest by 玛恩纳's 无动于衷: CB3's rule (every enemy damage instance) answers the
萨卡兹枯朽战车's pollution that hits a 卡西米尔 operator in CB1's timeline — with that counter limited to attacks the merged
roster-014 equals CB1's digest exactly (checked).

Gate: non-browser suites 5021 / 5021, npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok,
typecheck ok.
2026-10-07 03:54:35 +08:00

82 lines
4.9 KiB
JavaScript

// test/sim/feedback5-raid-revive.test.js — community report of 2026-10-06 (item 44) 「突袭盟约的干员如果带被动技能,复活之后不会
// 立马突袭出去,还要等突袭CD」. A 突袭 member counts a running deploy-timed skill ("部署后…秒内": 缄默德克萨斯 S1–S3 …) as 技能
// 就绪 (bonds/addon/battle.js raidPoll, GitHub #49 / #109), so after a redeploy it jumps at once when nothing is in its range.
// PRTS's 复活 (M3茧甲, 埃芒加德) is 退场 + a 0-time / 0-cost redeploy (PRTS 盟约记录 备注) — a new deployment — but the remake
// revived in place: her window, over by then, stayed over and she waited out the 10 s idle time. CB3 started a deploy-timed
// skill again after the in-place revive; since the CB1 merge the 复活 IS that redeploy (content/items/battle.js reviveNow:
// knocked out, back at once, free, on her tile), whose skill reset starts the window anew — the tests hold for both, and
// 不屈's real redeploy already did.
// Run: node --test test/sim/feedback5-raid-revive.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
const TEX = 'chess_char_4_16_a';
const DUMMY = { enemy_rv: enemyRec({ key: 'enemy_rv', hp: 1e9, speed: 0, mass: 0, atk: 0 }) };
const RAID = { raidShip: { count: 3, active: true, tier: 1, layers: 10 } };
/**
* 缄默德克萨斯 (S3, a 6 s deploy-timed skill) in 突袭 with `extra` (items / band): she jumps to a parked enemy, stays past her
* window, the enemy is removed, she is knocked out with nothing in range, a new enemy parks away from her. Returns how long
* after the knock-out her next jump comes (null: none within 15 s) and her skill state right after the revive.
*/
function reviveAndJump({ items, bandId = null, bonds = {} }) {
const h = makeBattle({
seed: 3, autoFinish: false, timeLimit: 120, hooks: ['deploy', 'death'], defs: { enemies: DUMMY }, bandId,
bonds: { ...RAID, ...bonds }, enemies: [{ key: 'enemy_rv', pos: [9, 9] }], units: [{ chessId: TEX, row: 12, col: 3, skillIndex: 2, items }],
});
h.step();
const u = h.unit(TEX);
const jumps = () => h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial);
assert.ok(h.runUntil(() => jumps().length >= 1, 10), 'her first jump');
h.run(8);
assert.equal(u.skill.active, false, 'her 6 s window is over');
for (const e of h.b.enemies.filter((x) => x.alive)) h.b.kill(e, null);
const t0 = h.b.time;
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
h.spawn('enemy_rv', { pos: [12, 9] });
h.step();
const state = { alive: u.alive, active: u.skill.active, timeLeft: u.skill.timeLeft };
const n = jumps().length;
const jumped = h.runUntil(() => jumps().length > n && u.tileR === 12, 15);
checkInvariants(h.b);
return { after: jumped ? h.b.time - t0 : null, state, u, h };
}
test('M3茧甲 / 埃芒加德 revive 缄默德克萨斯 in place: her deploy-timed S3 runs again and she jumps at once (until 0.2.0 only after the 10 s idle)', () => {
for (const [label, opts] of [['M3茧甲', { items: ['chess_item_4_12_e_a'] }], ['埃芒加德', { bandId: 'band_ermengard' }]]) {
const { after, state } = reviveAndJump(opts);
assert.ok(state.alive, `${label}: revived in place`);
assert.ok(state.active, `${label}: her deploy-timed S3 runs again`);
assert.ok(after != null && after < 1, `${label}: jumped ${after?.toFixed(2)} s after the knock-out`);
}
});
test('不屈\'s 立刻重新部署 (a real redeploy) already jumped at once — unchanged', () => {
const { after } = reviveAndJump({ bonds: { indomShip: { count: 2, active: true, tier: 1, layers: 300 } } });
assert.ok(after != null && after < 1, `jumped ${after?.toFixed(2)} s after the knock-out`);
});
test('an in-place revive inside the window sets it back to its full length; 砾 revived by M3茧甲 gets a fresh barrier', () => {
const h = makeBattle({ seed: 3, autoFinish: false, timeLimit: 60, units: [{ chessId: TEX, row: 12, col: 3, skillIndex: 2, items: ['chess_item_4_12_e_a'] }] });
h.step();
const u = h.unit(TEX);
h.run(3);
assert.ok(u.skill.active && u.skill.timeLeft < u.skill.duration - 2, 'half-way through the window');
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
h.step(2);
assert.ok(u.alive && u.skill.active && u.skill.timeLeft > u.skill.duration - 0.2, `back to the full ${u.skill.duration} s (${u.skill.timeLeft.toFixed(2)})`);
checkInvariants(h.b);
const g = makeBattle({ seed: 3, autoFinish: false, timeLimit: 60, units: [{ chessId: 'chess_char_2_12_a', row: 10, col: 4, items: ['chess_item_4_12_e_a'] }] });
g.step();
const gr = g.unit('chess_char_2_12_a');
g.run(12);
assert.ok(!gr.findBuff('gravel:rats'), 'the deployment barrier has run out');
g.b.dealDamage(null, gr, { amount: 1e9, type: 'true' });
g.step(2);
assert.ok(gr.alive && gr.findBuff('gravel:rats')?.shield > 0, 'a fresh barrier after the revive');
checkInvariants(g.b);
});