From d870103ce88a50bad7a5ba2cc5139bc2becc40e6 Mon Sep 17 00:00:00 2001 From: sganggs Date: Tue, 6 Oct 2026 16:07:05 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20WE1:=20one=20reading=20of=20charge?= =?UTF-8?q?=5Ftoken[born]=20=E2=80=94=20=E5=82=80=E5=BD=B1's=20redeploymen?= =?UTF-8?q?t=20readies=20a=20waiting=20=E9=95=9C=E4=B8=AD=E8=99=9A?= =?UTF-8?q?=E5=BD=B1=20at=20once,=20as=20=E5=87=AF=E5=B0=94=E5=B8=8C's=20M?= =?UTF-8?q?on3tr=20(follow-up=2020)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client's buff_template_data: charge_token[born] = ON_OWNER_BORN → RechargeToken (timing NORMAL); the summon's own charge_token[finish] = ON_OWNER_FINISH → RechargeToken (timing ON_FINISH) — PRTS 镜中虚影 "退场时返还1个可部署的镜中虚影", PRTS 傀影 备注 "部署召唤物后立刻开始计算再部署时间". Reading [ASSUMED: from the enum and the owner's first deployment]: NORMAL = usable now, ON_FINISH = after the summon's own redeploy time — the reading of every other summoner kit (谬因, 机械师, 涤火杰西卡, 麦哲伦 / 令 / 电弧, 白铁, 娜斯提, 凯尔希). - op-phatom.js: his (re)deployment (not a free move) readies a waiting twin at once (it comes back at the next retry, paying its cost); its own timer from its last deployment still holds it while he stands. op-kalts.js: the comment cites the shared reading. Golden: no scenario moved (no corpus battle redeploys 傀影 while his twin waits). Tests: test/content/feedback5-summon-recharge.test.js (new, 2); op_phatom, op_kalts pass. --- server/sim/content/kits/ops/op-kalts.js | 4 +- server/sim/content/kits/ops/op-phatom.js | 15 +++++-- .../content/feedback5-summon-recharge.test.js | 41 +++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 test/content/feedback5-summon-recharge.test.js diff --git a/server/sim/content/kits/ops/op-kalts.js b/server/sim/content/kits/ops/op-kalts.js index b09a283b..17eb7b30 100644 --- a/server/sim/content/kits/ops/op-kalts.js +++ b/server/sim/content/kits/ops/op-kalts.js @@ -33,7 +33,9 @@ // [ASSUMED: given here]); its DEF is ×0 while its tile is outside her attack range (kalts_t_1[token_def_down]; PHY-Y stage // 3: inside it ASPD +attack_speed, DEF +def — kalts_t_1[token_def_down_finish]). It is withdrawn when she leaves (no // rattle); it comes back on its tile its respawnTime (25 s) after it left, paying its cost (10 DP), only while she stands — -// her (re)deployment readies a waiting one at once (charge_token[born]) [ASSUMED: the 卫戍 auto redeploy of a placed summon]. +// her (re)deployment readies a waiting one at once (charge_token[born]: RechargeToken with timing NORMAL, while its own timer +// is the summon's charge_token[finish], ON_FINISH — the one reading of every summoner kit, 傀影's twin included) +// [ASSUMED: the 卫戍 auto redeploy of a placed summon]. // - T2 不毁重构: Mon3tr knocked out (not withdrawn; S3's 流失 counts) ⇒ every selectable enemy of its 3×3 (the talent's x-4, // air units too) takes `value` (1200) true damage (溅射, not dodgeable) and is stunned `stun` (3) s. PHY-X stage 3 (the // token's module talent: 3.5 s, 1500, hp_ratio): also once per deployment when a hit leaves it at or below hp_ratio of diff --git a/server/sim/content/kits/ops/op-phatom.js b/server/sim/content/kits/ops/op-phatom.js index d683b43b..0f9df20d 100644 --- a/server/sim/content/kits/ops/op-phatom.js +++ b/server/sim/content/kits/ops/op-phatom.js @@ -38,10 +38,10 @@ // each of its own deployments. When he leaves the field (knocked out, withdrawn, forced out) it is withdrawn (PRTS "强制 // 撤退"; the client's KillTokens); a twin off the field comes back on its tile — paying its cost (5 DP) as a deployment, // the 卫戍 auto redeploy of a placed summon [ASSUMED, as 鸿雪's 打字机 / 凯尔希's Mon3tr] — once its redeploy time has -// passed since its LAST DEPLOYMENT (PRTS 备注: the timer starts at the summon's deployment) and only while he stands (it -// is recharged by his deployment, withdrawn by his leaving) [ASSUMED: his redeployment does not cut the twin's own timer -// short — the client's RechargeToken NORMAL; 虚影精通 / EXE-X would matter little otherwise; the 凯尔希 kit reads the same -// charge_token[born] as "readies at once"]. EXE-Y stage 2+ "本体和虚影同时在场时,攻击力各+10%": the twin's hidden module +// passed since its LAST DEPLOYMENT (PRTS 备注: the timer starts at the summon's deployment; the twin's charge_token[finish] +// recharges it ON_FINISH) and only while he stands; his (re)deployment readies a waiting twin at once (his +// charge_token[born]: RechargeToken with timing NORMAL — read the same way for every summoner, 凯尔希's Mon3tr included; +// until 0.2.0 this kit let the twin's own timer run on). EXE-Y stage 2+ "本体和虚影同时在场时,攻击力各+10%": the twin's hidden module // talent `atk` — ATK +atk on both while both are on the field. // - T2 虚影精通 "虚影的再部署时间-10秒" (EXE-X stage 3: −16): the twin's own talent respawn_time, added to its 45 s. // - S1 暗夜魅影 (被动, at each deployment): physical dodge `prob` and a 屏障 of hp_ratio × max HP that absorbs physical damage @@ -210,6 +210,13 @@ export default { // "傀影退场时强制撤退场上的镜中虚影" (KillTokens): a withdrawal, not a knock-out for (const t of twinsOf(battle, unit)) if (t.alive) battle.retreat(t, { reason: 'retreat' }); }, { owner: unit }); + // charge_token[born] (RechargeToken, timing NORMAL): his (re)deployment readies a waiting twin at once — the + // twin's own timer is the ON_FINISH recharge of its charge_token[finish] (as every summoner kit reads it) + // [ASSUMED: NORMAL = usable now, from the enum and the first deployment] + battle.on('deploy', (c) => { + if (c.unit !== unit || c.move) return; + for (const t of twinsOf(battle, unit)) if (!t.alive && !t.removed) t.mem.twinReadyAt = battle.time; + }, { owner: unit }); // 本体和虚影同时在场时,攻击力各+N%: the twin's hidden module talent (his text when no twin def resolves) const both = num(tokTalentBb(battle.tokenDef(TWIN, unit), 'atk').atk, /本体和虚影同时在场时/.test(t0desc) ? num(+(/攻击力各\+(\d+)%/.exec(t0desc)?.[1]) / 100) : 0); diff --git a/test/content/feedback5-summon-recharge.test.js b/test/content/feedback5-summon-recharge.test.js new file mode 100644 index 00000000..39cb1a9c --- /dev/null +++ b/test/content/feedback5-summon-recharge.test.js @@ -0,0 +1,41 @@ +// test/content/feedback5-summon-recharge.test.js — one reading of the summoners' charge_token[born] (follow-up 20): the client's +// RechargeToken on the owner's birth runs with timing NORMAL — the summon is ready at once —, while the summon's own +// charge_token[finish] (ON_FINISH) is the timer that runs from its deployment (PRTS 傀影 备注 "部署召唤物后立刻开始计算再部署时间"). +// 傀影's 镜中虚影 (op-phatom.js) now reads it as 凯尔希's Mon3tr (op-kalts.js) and the other summoner kits do: when the owner +// is deployed again, a summon still waiting for its own timer comes back at once (paying its cost). +// Run: node --test test/content/feedback5-summon-recharge.test.js + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { makeBattle, checkInvariants } from '../helpers/battleHarness.js'; + +/** The owner (uid 1, a tier-5 自选 piece) and its placed summon (uid 2). */ +function field(charId, tokenId) { + const h = makeBattle({ + timeLimit: 900, autoFinish: false, seed: 5, flags: { dpInit: 99, dpPerSec: 0, dpMax: 999 }, + units: [{ uid: 1, diy: { slot: 'chess_char_5_diy1_a', charId, skillIndex: 0 }, row: 10, col: 2 }, { uid: 2, kind: 'token', tokenId, ownerUid: 1, row: 10, col: 4 }], + }); + h.step(); + return { h, u: h.unit(1), t: h.unit(2) }; +} + +for (const [name, charId, tokenId] of [['傀影 / 镜中虚影', 'char_250_phatom', 'token_10007_phatom_twin'], ['凯尔希 / Mon3tr', 'char_003_kalts', 'token_10002_kalts_mon3tr']]) { + test(`${name}: the owner's redeployment readies a waiting summon at once (charge_token[born], timing NORMAL); its own timer still holds it while the owner stands`, () => { + const { h, u, t } = field(charId, tokenId); + assert.ok(u.alive && t.alive, 'both deployed'); + h.run(2); + // knocked out with the owner standing: its own timer (from its deployment) holds it + h.b.kill(t, null); + h.run(3); + assert.ok(!t.alive, 'waits for its own timer'); + // the owner leaves and comes back long before that timer has run + h.b.retreat(u); + assert.ok(h.b.redeploy(u, { free: true }), 'the owner back'); + const dp = h.b.players[0].dp; + assert.ok(h.runUntil(() => t.alive, 1), 'the summon comes back at once with the owner'); + assert.ok(h.b.time < 10, `at ${h.b.time.toFixed(2)} s — its own timer (≥ 25 s) has not run`); + assert.equal(h.b.players[0].dp, dp - t.base.cost, 'paying its cost'); + checkInvariants(h.b); + assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); + }); +}