diff --git a/docs/design/ACTION_VOCABULARY.md b/docs/design/ACTION_VOCABULARY.md index d2c48011..3b6330fd 100644 --- a/docs/design/ACTION_VOCABULARY.md +++ b/docs/design/ACTION_VOCABULARY.md @@ -105,7 +105,7 @@ - シンボル・コストの変更は `timedEffect` の内容 `symbolAdd`(同じ色を1つ追加)/`symbolSet { color, count }`(◯色◯つとして扱う)/`symbolLoss { color? }`(指定色を1つ失う。省くと使う人が選ぶ)/`cost { amount }`(増減。元のコストは残らない)。「すべて」のシンボル喪失は `all: true` で、Lv と同じく後から場に出た個体にも書き込む(2026-09-24) - アタック時⇔ブロック時の効果の付け替えは `timedEffect` の内容 `{ type: "triggerSwap"; from }`。1体は個体の印、`all: true` は `side: "both"`=ゲーム全体の印・`side: "own"`=プレイヤーに掛かる制約(どちらも判定のたびに見るので後から出たスピリットにも効く)(2026-09-24) - 「このバトルの間、BPの代わりに Lv/コアの数/コストを比べる」は `timedEffect` の内容 `{ type: "compareBy"; by }`、「BPの高い方が破壊される」は `{ type: "invertBattleWinner" }`(期間 `battle`・対象はバトル)。印は `state.battle` に置き、バトル外は不発(2026-09-25) -- 「このターンの間、可能ならば必ずアタックする」は `timedEffect` の内容 `{ type: "mustAttack" }`(期間 `turn`)。1体は個体の印 `mustAttackThisTurn`(既に印がある個体は候補にしない)、「すべて」は `timedRule` で後から出たスピリットにも効く。判定は `shared/rules.ts` の `mustAttackThisTurn`。「好きなだけ指定」(BS12-079)だけ旧 `forceAttackThisTurn` に残す(2026-09-25) +- 「このターンの間、可能ならば必ずアタックする」は `timedEffect` の内容 `{ type: "mustAttack" }`(期間 `turn`)。1体は個体の印 `mustAttackThisTurn`(既に掛かっている個体もそのまま選べる。トリガー抑止も同じ。2026-09-25 ユーザー確認)、「すべて」は `timedRule` で後から出たスピリットにも効く。判定は `shared/rules.ts` の `mustAttackThisTurn`。「好きなだけ指定」(BS12-079)だけ旧 `forceAttackThisTurn` に残す(2026-09-25) - 「このターンの間、〜の効果は発揮されない」は `timedEffect` の内容 `{ type: "suppressTrigger"; trigger }`(期間 `turn`)。1体は個体の `suppressedTriggersThisTurn`、`all: true` は陣営ごとの `state.triggerSuppressionThisTurn`(陣営は `side`、絞り込みは未対応)。判定は `triggers.ts` の `isTriggerSuppressed`(2026-09-25) - 「このターンの間、疲労状態でもブロックできる」は `timedEffect` の内容 `{ type: "canBlockWhileRested" }`(期間 `turn`・`side: "own"`)。1体は自分のスピリットから選んで個体の印 `canBlockWhileRestedThisTurn`、`all: true` は `timedRule`。判定は `shared/rules.ts` の `canBlockWhileRestedThisTurn`(2026-09-25) - 「このターンの間、自分のスピリットに“誘発効果”を与える」は `timedEffect` の内容 `{ type: "grantTrigger"; trigger; action; battleRole? }`(期間 `turn`・`side: "own"`)。1体は自分のスピリットから選んで個体の `tempGrantedTriggers`(`triggers.ts` の `fireTrigger` が静的な効果と合わせて読む)。「すべてに与える」はステップの見出しを期間とする継続効果 `effectGrant`(`phaseTurn`)で書き、`timedEffect` の `all` は未対応(2026-09-25) diff --git a/scripts/smoke/part377.ts b/scripts/smoke/part377.ts index 4e33a02a..52978488 100644 --- a/scripts/smoke/part377.ts +++ b/scripts/smoke/part377.ts @@ -51,7 +51,7 @@ console.log("=== 1. 1体:合体していないスピリットだけが候補 assert(combined.mustAttackThisTurn !== true, "合体スピリットは選ばれない") } -console.log("=== 2. 1体:既に印がある個体は候補にしない(機械神の加護を2回) ===") +console.log("=== 2. 1体:既に掛かっている個体も選べる(機械神の加護を2回。2026-09-25 ユーザー確認) ===") { const s = game() const strong = createInstance(VANILLA, 1, 3) @@ -60,7 +60,10 @@ console.log("=== 2. 1体:既に印がある個体は候補にしない(機 refreshLevelAsOverrides(s) resolveAction(s, "p1", null, mustAttackAction("SD01-032")) resolveAction(s, "p1", null, mustAttackAction("SD01-032")) - assert(strong.mustAttackThisTurn === true && weak.mustAttackThisTurn === true, "2回目は別の1体に付く") + assert(strong.mustAttackThisTurn === true && weak.mustAttackThisTurn !== true, "自動選択では2回目も実効BP最大の同じ1体") + s.interactiveTargets = true + resolveAction(s, "p1", null, mustAttackAction("SD01-032")) + assert(s.pendingChoice?.candidates.includes(strong.instanceId) === true, "対話では既に掛かっている個体も候補に出る") } console.log("=== 3. すべて:解決後に場に出たコスト3以下にも効く(アンブッシュブロッカー) ===") diff --git a/scripts/smoke/part378.ts b/scripts/smoke/part378.ts index 7faf9f07..02fa857f 100644 --- a/scripts/smoke/part378.ts +++ b/scripts/smoke/part378.ts @@ -52,8 +52,9 @@ console.log("=== 2. 1体:月光姫マーニ=実効BP最大の相手1体の resolveAction(s, "p1", null, suppressAction("BS14-043")) assert(strong.suppressedTriggersThisTurn?.includes("onAttack") === true, "実効BP最大の1体に印が付く") assert(weak.suppressedTriggersThisTurn === undefined, "もう1体には付かない") + s.interactiveTargets = true resolveAction(s, "p1", null, suppressAction("BS14-043")) - assert(weak.suppressedTriggersThisTurn?.includes("onAttack") === true, "既に印がある個体は候補にしない") + assert(s.pendingChoice?.candidates.includes(strong.instanceId) === true, "既に止められている個体も候補に出る(2026-09-25 ユーザー確認)") } console.log("すべてのチェックに合格しました 🎉(part378)") diff --git a/server/src/logic/actions/timedEffect.ts b/server/src/logic/actions/timedEffect.ts index 815abf03..6a220810 100644 --- a/server/src/logic/actions/timedEffect.ts +++ b/server/src/logic/actions/timedEffect.ts @@ -20,9 +20,10 @@ function flagOf(content: Content, duration: TimedEffect["duration"]) { } // BP は重ねがけできるので「既に持つ」とは見ない +// 強制アタック・トリガー抑止は、既に掛かっている個体もそのまま選べる(2026-09-25 ユーザー確認) function has(inst: CardInstance, action: TimedEffect): boolean { return action.content.every((c) => { - if (c.type === "suppressTrigger") return inst.suppressedTriggersThisTurn?.includes(c.trigger) === true + if (c.type === "mustAttack" || c.type === "suppressTrigger") return false const flag = flagOf(c, action.duration) return flag !== null && inst[flag] === true })