diff --git a/docs/SIM.md b/docs/SIM.md
index 1e72ed5a..a5e8e019 100644
--- a/docs/SIM.md
+++ b/docs/SIM.md
@@ -390,11 +390,14 @@ catalogue status the same rule (one invisible buff `key` per target whatever app
or icon): the engine default for two same-named buffs (PRTS 作战机制 "同名buff的默认叠加策略buff只能表现出一个"). 奥术 uses
it, so the two players of a pair field compete for one instance instead of multiplying, and so do 灵知's 坚冰 and the
莱恩哈特 / 缄默德克萨斯 RES cuts, once keyed per unit (DESIGN §20.10). "Strongest" = the largest |value|. PRTS 盟约记录's
-奥术 note "※同一单位仅可对同一目标同时施加1个该盟约法术伤害提升效果" (2026-03-30 revision: "…持续期间内,无法对该目标施加新的…")
-limits each unit to one instance per target; read with the engine default — a newer same-named buff waits inert until
-the earlier ones end (PRTS 常见同名状态 "默认叠加方式") — and 巴哈姆特 12316 ("共享型buff會跟對面搶"), one instance is effective
-at a time, which is what applyStrongest keeps; strongest rather than earliest, and a unit refreshing its own instance,
-stay [ASSUMED] (feedback after 0.1.0).
+奥术 note "※同一单位仅可对同一目标同时施加1个该盟约法术伤害提升效果" limits each unit to one instance per target; read with
+the engine default — a newer same-named buff waits inert until the earlier ones end (PRTS 常见同名状态 "默认叠加方式") — and
+巴哈姆特 12316 ("共享型buff會跟對面搶"), one instance is effective at a time, which is what applyStrongest keeps; strongest
+rather than earliest is [ASSUMED]. Known deviation [ASSUMED]: an earlier revision of that note (oldid 386936) read
+"同一单位在对同一目标施加的“法术伤害提升”效果的持续期间内,无法对该目标施加新的该盟约的法术伤害提升效果" — no re-application
+while the unit's own instance lasts, so it would lapse 3 s after the first hit and return with the next arts hit —
+whereas an equal-value hit here refreshes the instance to a fresh 3 s; slightly stronger (at most one attack interval
+of coverage per 3 s), kept because the current wording no longer says so (feedback after 0.1.0).
| key | effect | value |
|---|---|---|
diff --git a/public/js/ui/bondStrip.js b/public/js/ui/bondStrip.js
index 468cba5f..04d53836 100644
--- a/public/js/ui/bondStrip.js
+++ b/public/js/ui/bondStrip.js
@@ -60,10 +60,11 @@ export function BondStrip({ bonds, layersDisabled = false, onOpen, openId = null
/**
* Bond detail popup.
* @param {{ bondId: string, entry?: any, priv?: any, banned?: string[], onClose: Function, onMember?: (chessId:string)=>void,
- * place?: 'left'|'beside'|'besideR'|'right'|null, over?: boolean, beside?: 'left'|'right'|null, owner?: string|null }} props —
+ * place?: 'left'|'beside'|'besideR'|'right'|null, over?: boolean, beside?: 'left'|'right'|null, owner?: string|null, off?: boolean }} props —
* `place`: where it opens (gameLogic bondPopupPlace; `beside: 'left'` = the older spelling of 'beside'); `over`: above the
* detail card; `owner`: the watched teammate's name (`entry` / `priv` are then theirs: ui/watchBonds.js); `off`: the mode
- * never activates this bond (gameLogic modeOffBonds — 标准's 10 inactive bonds): 本局禁用 instead of 未激活, with a note
+ * never activates this bond (gameLogic modeOffBonds — 标准's 10 inactive bonds): 本局禁用 instead of 未激活, with a note, no
+ * 在场 count and no 当前效果 block (its numbers would promise an effect the mode never gives; the bond text stays)
*/
export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember, place = null, over = false, beside = null, owner = null, off = false }) {
const b = data.lookup('bonds', bondId);
@@ -76,7 +77,7 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
const members = bondMembers(b, priv, banned, (id) => data.lookup('chess', id));
const countsHand = entry?.countsHand ?? b.countsHand;
const next = nextThreshold(count, th);
- const hasNow = !!(b.effectDescRaw || b.effectDesc);
+ const hasNow = !off && !!(b.effectDescRaw || b.effectDesc);
const at = place || (beside === 'left' ? 'beside' : 'left');
return html`
diff --git a/server/sim/content/bonds/addon/battle.js b/server/sim/content/bonds/addon/battle.js
index f7a94291..b1e41a26 100644
--- a/server/sim/content/bonds/addon/battle.js
+++ b/server/sim/content/bonds/addon/battle.js
@@ -19,9 +19,11 @@
// instances multiplied (×5.4 × ×5.6 on a leader at ~250 layers, DESIGN §20.10). PRTS 盟约记录's own
// note "※同一单位仅可对同一目标同时施加1个该盟约法术伤害提升效果" (one instance per unit and target)
// fits it under the engine default (a newer same-named buff waits until the earlier ends, PRTS
-// 常见同名状态); strongest vs earliest and a unit refreshing its own stay [ASSUMED]. Re-measured after
-// the 0.1.0 report "奥术盟约不生效": it works on the real and the browser path
-// (test/sim/feedback1b-arcane.test.js); 标准 never activates it (modeDataDict inactiveBondIdList)
+// 常见同名状态); strongest vs earliest is [ASSUMED]. Known deviation [ASSUMED]: an earlier revision
+// (oldid 386936) forbade a unit re-applying while its own instance lasts; an equal hit here
+// refreshes it to a fresh 3 s (slightly stronger). Re-measured after the 0.1.0 report
+// "奥术盟约不生效": it works on the real and the browser path (test/sim/feedback1b-arcane.test.js);
+// 标准 never activates it (modeDataDict inactiveBondIdList)
// 坚守 steadShip all operators max HP +(base + per·L); tier 2: 40 % of a non-member operator's damage is borne by
// the members on the field (split evenly, sourceless true damage — already mitigated), members’ thorns
// (base + per·L arts, sourceless but credited to the member hit, ≤ 1 per cd_duration per member)
diff --git a/test/match/feedback1b-items.test.js b/test/match/feedback1b-items.test.js
index 8ffc2541..97a9b352 100644
--- a/test/match/feedback1b-items.test.js
+++ b/test/match/feedback1b-items.test.js
@@ -11,6 +11,7 @@ import assert from 'node:assert/strict';
import { DATA, makeMatch, give, giveItem, legalTileFor, checkInvariants } from './harness.js';
import { tileKey } from '../../server/match/board.js';
import { applyCard } from '../../server/match/choices.js';
+import { makeCtx } from '../../server/match/effectsMeta.js';
const WHITE = 'chess_item_1_05_e_a';
const GOLD = 'chess_item_1_05_e_b';
@@ -134,6 +135,22 @@ test('a promotion that returns equipment merges a returned pair (latent: no norm
checkInvariants(m);
});
+test('an effect that removes an operator (ctx.destroyPiece) merges a returned pair (latent: no normal state holds a pair)', () => {
+ const { m, ps } = prep();
+ const [a] = deploy(m, ps, [OP]);
+ // built directly: the carrier's white and a white in the hand — every entry path would have merged them at once
+ a.items = [ps.newPiece('item', WHITE)];
+ ps.hand[ps.hand.findIndex((x) => x == null)] = ps.newPiece('item', WHITE);
+ ps.recompute();
+ // the path of 突变细胞 / the recruit-and-gift item / the round-start promotion item (builtinMeta, content/items/meta.js)
+ const ctx = makeCtx(m, ps, { kind: 'item', key: 'item:test' }, 'onRoundStart');
+ assert.equal(ctx.destroyPiece(a.uid), true);
+ assert.equal(ps.find(a.uid), null, 'the operator is gone');
+ assert.deepEqual(count(ps), { white: 0, gold: 1 }, '"在失去该干员…" returns the equipment, then the auto-merge');
+ checkInvariants(m);
+ m.dispose();
+});
+
test('random intents around 源石溶剂 (buy, equip / replace, sell, move, destroy) conserve W + 2·G and never leave a pair', () => {
let seed = 11;
const rnd = () => { seed = (seed * 1103515245 + 12345) >>> 0; return seed / 4294967296; };
diff --git a/test/ui/feedback1b-bonds.test.js b/test/ui/feedback1b-bonds.test.js
index 9f05b9d9..fc30b409 100644
--- a/test/ui/feedback1b-bonds.test.js
+++ b/test/ui/feedback1b-bonds.test.js
@@ -94,15 +94,20 @@ test('shop / reward card: a mode-disabled bond tag is struck through with the
assert.ok(tags(null).every((v) => !hasClass(v, 'is-off')), 'no set: as before');
});
-test('bond popup: 本局禁用 instead of 未激活, a note, and no misleading member count', () => {
+test('bond popup: 本局禁用 instead of 未激活, a note, and no misleading member count or 当前效果 numbers', () => {
const pop = BondPopup({ bondId: 'arcaneShip', entry: null, priv: { board: [], hand: [] }, onClose() {}, off: true });
const text = textOf(pop);
assert.match(text, /本局禁用/);
assert.match(text, /本模式下该盟约不会激活/);
assert.doesNotMatch(text, /在场/);
+ // the 当前效果 block ("提升20% … 68%") would promise an effect the mode never gives; the bond text (盟约效果) stays
+ assert.doesNotMatch(text, /当前效果/);
+ assert.ok(![...walk(pop)].some((v) => hasClass(v, 'bpop__sec--now')), 'no current-effect section');
+ assert.match(text, /盟约效果/);
const plain = textOf(BondPopup({ bondId: 'arcaneShip', entry: null, priv: { board: [], hand: [] }, onClose() {} }));
assert.match(plain, /未激活/);
assert.match(plain, /在场/);
+ assert.match(plain, /当前效果/, 'an enabled bond keeps its current-effect block');
});
test('the game screen hands the mode-disabled set to the shop bar, the detail card and the bond popup', () => {