feat(buffs-power): 밸런스 시뮬 버프/디버프·Power 동기화

- calcAttack(힘·약화·취약) 공식 export + 단위 테스트
- 적 Debuff 인텐트·플레이어/적 디버프 감소 타이밍 Lua 동기화
- Power 등록·매턴 발동·소멸 재현, chooseAction 파워 우선
- 신규 테스트 6건 (총 21건 통과)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 01:32:31 +09:00
parent 00844857ee
commit 0a96a6955a
2 changed files with 128 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch,
mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack,
} from './sim-balance.mjs';
test('applyDamage: 방어 우선 차감 후 hp', () => {
@@ -131,3 +131,72 @@ test('simulateCombat: 복합 카드(공격+방어) 블록이 적 공격을 흡
assert.equal(r.draw, true);
assert.equal(r.playerHpRemaining, 80);
});
test('calcAttack: 힘·약화·취약 공식 (Lua CalcPlayerAttack·DealDamageToTarget 동기화)', () => {
assert.equal(calcAttack(6, 0, 0, 0), 6); // 기본
assert.equal(calcAttack(6, 2, 0, 0), 8); // 힘+2
assert.equal(calcAttack(6, 0, 1, 0), 4); // 약화 floor(6*0.75)
assert.equal(calcAttack(6, 0, 0, 1), 9); // 취약 floor(6*1.5)
assert.equal(calcAttack(10, 2, 1, 1), 13); // floor(floor(12*0.75)=9 → floor(9*1.5))=13
});
test('simulateCombat: 적 Debuff 인텐트만 사용 → 플레이어 무피해', () => {
const data = {
cards: { Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 } },
starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'],
monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Debuff', effect: 'weak', value: 1 }] }],
};
const r = simulateCombat(data, mulberry32(1));
assert.equal(r.playerHpRemaining, 80);
});
test('simulateCombat: 플레이어 약화 시 공격 피해 감소 반영', () => {
// 약화 영구 부여 적: 4피해 카드가 floor(4*0.75)=3으로 감소
const data = {
cards: { Hit: { name: '타격', cost: 3, kind: 'Attack', damage: 4 } },
starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'],
monsters: [{ name: '적', maxHp: 10, intents: [{ kind: 'Debuff', effect: 'weak', value: 99 }] }],
};
const r = simulateCombat(data, mulberry32(1));
// 턴1: 4 (약화 전), 이후 매턴 3 → 10피해 도달 = 턴3 (4+3+3)
assert.equal(r.win, true);
assert.equal(r.turns, 3);
});
test('simulateCombat: 카드 취약 부여가 같은 카드 피해에 선적용 (Lua 동기화)', () => {
const data = {
cards: { CB: { name: '차지', cost: 3, kind: 'Attack', damage: 8, vuln: 2 } },
starterDeck: ['CB', 'CB', 'CB', 'CB', 'CB'],
monsters: [{ name: '적', maxHp: 12, intents: [{ kind: 'Defend', value: 0 }] }],
};
const r = simulateCombat(data, mulberry32(1));
// 취약 선적용이면 floor(8*1.5)=12 → 1턴 처치. 후적용이면 8 → 2턴.
assert.equal(r.turns, 1);
});
test('simulateCombat: Power(매턴 힘) 누적', () => {
const data = {
cards: {
Rage: { name: '분노', cost: 1, kind: 'Power', powerEffect: 'strengthPerTurn', value: 5 },
Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 },
},
starterDeck: ['Rage', 'Hit', 'Hit', 'Hit', 'Hit'],
monsters: [{ name: '적', maxHp: 60, intents: [{ kind: 'Defend', value: 0 }] }],
};
const r = simulateCombat(data, mulberry32(1));
assert.equal(r.win, true);
assert.ok(r.turns <= 6, `파워 누적으로 빠른 처치 기대, 실제 ${r.turns}`);
});
test('simulateCombat: 적 약화 인텐트 → 적 공격력 감소는 적용 안 됨(적 자신은 약화 안 걸림)', () => {
// 회귀 가드: Debuff 인텐트는 플레이어에게만 적용
const data = {
cards: { Skip: { name: '대기', cost: 3, kind: 'Skill', block: 0 } },
starterDeck: ['Skip', 'Skip', 'Skip', 'Skip', 'Skip'],
monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Debuff', effect: 'vuln', value: 1 }, { kind: 'Attack', value: 10 }] }],
};
const r = simulateCombat(data, mulberry32(1));
// 턴1: 취약1 부여 → 플레이어 취약. 턴1 종료 시 1 감소 → 0. 턴2: 공격 10 (취약 소멸) → 정확히 10만 피해.
// MAX_TURNS 동안 2턴 주기 공격 → 사망까지 충분 → win=false
assert.equal(r.win, false);
});