From 222ed92807c066ca2a73f0983d60da4b08f2e157 Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 29 Jun 2026 18:58:10 +0900 Subject: [PATCH] =?UTF-8?q?fix(balance):=20firstShivDamageBonus=20?= =?UTF-8?q?=EC=8B=9C=EB=AE=AC=EC=9D=84=20=EC=B2=AB=20Shiv=EC=97=90?= =?UTF-8?q?=EB=A7=8C=20=EC=A0=81=EC=9A=A9=20(Lua=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PhantomBlades(환영검: 첫 Shiv +9) 사용 시 Lua는 첫 Shiv 처리 후 ShivFirstDamageBonusUsed를 set(Attack 경로)하는데, JS 시뮬은 이 플래그 set이 else(비-Attack/Skill) 분기에 있어 Shiv(kind=Attack)는 도달 못 함 → 플래그 영영 false → 모든 Shiv가 +9를 받아 시뮬이 데미지를 과대집계. Lua가 정답(게임 정상) — 시뮬만 수정: 죽은 else-분기 플래그 set 제거 + Attack 분기(baseDamage 계산 직후, Lua 순서와 동일)에 추가. RED-GREEN 테스트로 턴당 첫 Shiv만 보너스 검증. 87개. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj --- tools/balance/sim-balance.mjs | 6 +++--- tools/balance/sim-balance.test.mjs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/balance/sim-balance.mjs b/tools/balance/sim-balance.mjs index c783ac6..789f074 100644 --- a/tools/balance/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -423,6 +423,9 @@ export function simulateCombat(data, rng, stats) { const hitN = (c.hits || 1) + bonusHits; let useAoe = c.aoe === true; if (c.class === 'shiv' && shivAoeThisCombat === true) useAoe = true; + if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) { + shivFirstDamageBonusUsed = true; + } const perHit = calcAttack(baseDamage || 0, pStr, pWeak, 0) * turnAttackMultiplier; const dealToTarget = (target, amount) => { if (!target || !target.alive) return { killed: false, dealt: 0 }; @@ -516,9 +519,6 @@ export function simulateCombat(data, rng, stats) { } } } - if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) { - shivFirstDamageBonusUsed = true; - } } } if (c.strength) pStr += c.strength; diff --git a/tools/balance/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs index fe44e5e..3583a11 100644 --- a/tools/balance/sim-balance.test.mjs +++ b/tools/balance/sim-balance.test.mjs @@ -902,6 +902,22 @@ test("calcEnemyAttack: 힘 손실이 base 아래로 공격을 낮춘다 (음수 assert.equal(calcEnemyAttack(5, 0, 0, 0, 6), 0); // 5-6=-1 → 0 클램프 }); +test('simulateCombat: firstShivDamageBonus는 턴당 첫 Shiv에만 적용 (Lua 동기화)', () => { + // PhantomBlades(firstShivDamageBonus 9) 활성. 턴당 3 Shiv 사용(에너지3·cost1). + // 정답(첫 Shiv만 +9): 턴1 = 10+1+1=12 → 13HP에 1 남김 → 2턴. + // 버그(모든 Shiv +9): 턴1 = 10*3=30 → 1턴. + const data = { + cards: { + PhantomBlades: { name: '환영검', cost: 0, kind: 'Power', firstShivDamageBonus: 9 }, + Shiv: { name: '시브', cost: 1, kind: 'Attack', class: 'shiv', damage: 1 }, + }, + starterDeck: ['PhantomBlades', 'Shiv', 'Shiv', 'Shiv', 'Shiv'], + monsters: [{ name: '적', maxHp: 13, intents: [{ kind: 'Attack', value: 0 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + assert.equal(r.turns, 2); +}); + test("simulateCombat: repeatOnKill repeats an attack until no kill occurs", () => { const shared = { cards: {