diff --git a/server/sim/content/kits/tier3.js b/server/sim/content/kits/tier3.js index f8154762..cbb7a8a2 100644 --- a/server/sim/content/kits/tier3.js +++ b/server/sim/content/kits/tier3.js @@ -406,7 +406,8 @@ const KITS = { // a small push, radial despite the text's 向前 — PRTS 备注 "推开效果为径向推动"; client charpack char_1033_swire2: // the RandomGold ability (Skill_3_End) carries swire2_s_3[knockback] of template knockback[relative]; 地面敌方单位, // 弹道不可对空). It does not close itself when the purse is full (PRTS 卫戍协议/帮助 「通常不会自动关闭技能」; the skill - // text is 「可随时主动关闭」; GitHub #89-1). The player closes it: skill.end('manual'), which spends the coins. + // text is 「可随时主动关闭」). Owner's decision 2026-10-04: the written cap (金币上限为10) is the close — at the cap the + // coins are shot at once (skill.end('manual')), with or without a mark. Below the cap it stays open. // 精锐 module MER-Y: ATK +4 % per trait payment (≤ 5 stacks). chess_char_3_04_a: (bb, chess, def) => { const d = defOf(chess, def); @@ -501,11 +502,16 @@ const KITS = { for (const e of unit.blocking || []) if (e.alive && !e.isFlying && !list.includes(e)) list.push(e); return list; }; + const purseCap = Math.max(1, Math.floor(num(s.bb.sp, 10))); return { kind: 'toggle', attack: { hits: 2 }, - // PRTS 卫戍协议/帮助 「通常不会自动关闭技能」; 千金一掷 「可随时主动关闭」 (GitHub #89-1). A full purse - // with a markable enemy does not end it. skill.end('manual') is the close that spends the coins. + // Owner 2026-10-04: 「金币上限为10」 is the close. Reaching the cap shoots the coins (the onEnd below). + // It does not wait for a mark — a cap with nobody in range still spends them and hits nothing. Below the + // cap the skill stays open (the old close also required a markable enemy; that wait is withdrawn). + onTick({ unit, skill }) { + if (skill.active && (unit.mem.coins ?? 0) + 1e-9 >= purseCap) skill.end('manual'); + }, onEnd({ battle, unit, reason }) { if (reason !== 'manual' || !unit.alive) return; const n = unit.mem.coins ?? 0; diff --git a/test/sim/feedback3-swire-s3.test.js b/test/sim/feedback3-swire-s3.test.js index ef89a17e..3e9803ec 100644 --- a/test/sim/feedback3-swire-s3.test.js +++ b/test/sim/feedback3-swire-s3.test.js @@ -1,8 +1,6 @@ -// test/sim/feedback3-swire-s3.test.js — GitHub #89-1: 琳琅诗怀雅 S3 千金一掷 used to call skill.end('manual') -// from onTick once her coins were full and a ground enemy stood in the coin range. PRTS 卫戍协议/帮助 技能操作 -// 「通常不会自动关闭技能」 and the skill text 「可随时主动关闭技能;携带此技能时金币上限为10」: she keeps it -// open. The close is still skill.end('manual') — the same call the old onTick used, and feedback1d-push.test.js — -// which spends the coins and pushes. No markable target still ends and does not throw. +// test/sim/feedback3-swire-s3.test.js — 琳琅诗怀雅 S3 千金一掷. Owner 2026-10-04: the written purse cap +// (「金币上限为10」) is the close. At the cap the coins are shot (skill.end('manual'): damage + radial push). +// Below the cap the skill stays open. No mark still spends and hits nothing. import { test } from 'node:test'; import assert from 'node:assert/strict'; @@ -29,32 +27,47 @@ const arm = (h) => { return u; }; -test('琳琅诗怀雅 S3 stays open with a full purse and a coin target; skill.end(\'manual\') still spends', REAL, () => { +test('琳琅诗怀雅 S3 shoots the purse when it reaches the cap of 10, and stays open below it', REAL, () => { + const under = battle(true); + const u9 = arm(under); + u9.mem.coins = 9; + // shorter than the merchant trait's 3 s payment, which would add a coin and reach the cap + under.run(2); + assert.equal(u9.skill.active, true, '9 coins do not close it'); + assert.equal(u9.mem.coins, 9); + const h = battle(true); const u = arm(h); - const full = 10; // S3 blackboard sp: the purse cap 「金币上限为10」 - u.mem.coins = full; - h.run(6); - assert.equal(u.skill.active, true, 'several seconds do not close it'); - assert.equal(u.mem.coins, full, 'the coins are still hers'); const e = h.enemies()[0]; const hp0 = e.hp; - u.skill.end('manual'); - assert.equal(u.skill.active, false); - assert.equal(u.mem.coins, 0, 'the close spends every coin'); - assert.ok(e.hp < hp0, 'and the coins hit the enemy in range'); + u.mem.coins = 10; + h.run(0.2); + assert.equal(u.skill.active, false, 'the cap shoots'); + assert.equal(u.mem.coins, 0, 'every coin is spent'); + assert.ok(e.hp < hp0, 'the coins hit the enemy in range'); assert.equal(h.b.errors.length, 0); checkInvariants(h.b); }); -test('琳琅诗怀雅 S3: closing with no markable target still ends and spends, and does not throw', REAL, () => { +test('琳琅诗怀雅 S3: the cap with no markable target still ends and spends, and does not throw', REAL, () => { const h = battle(false); const u = arm(h); - u.mem.coins = 4; - assert.doesNotThrow(() => u.skill.end('manual')); + u.mem.coins = 10; + assert.doesNotThrow(() => h.run(0.2)); assert.equal(u.skill.active, false); assert.equal(u.mem.coins, 0); assert.equal(h.hooksOf('damaged').length, 0, 'nothing to pay'); assert.equal(h.b.errors.length, 0); checkInvariants(h.b); }); + +test('琳琅诗怀雅 S3: a manual close below the cap still spends', REAL, () => { + const h = battle(false); + const u = arm(h); + u.mem.coins = 4; + assert.doesNotThrow(() => u.skill.end('manual')); + assert.equal(u.skill.active, false); + assert.equal(u.mem.coins, 0); + assert.equal(h.hooksOf('damaged').length, 0); + checkInvariants(h.b); +});