From aaa68ebe07e7b500ac185ee7b799e2afe7c58f19 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 23:40:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(card-frames):=20=EB=B3=B4=EC=83=81=20?= =?UTF-8?q?=EB=93=B1=EA=B8=89=20=EA=B0=80=EC=A4=91=20=EC=B6=94=EC=B2=A8=20?= =?UTF-8?q?70/25/5=20(Lua=20+=20JS=20=EB=AF=B8=EB=9F=AC=20rarityForRoll?= =?UTF-8?q?=C2=B7=EA=B2=BD=EA=B3=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- tools/balance/sim-balance.mjs | 7 +++++++ tools/balance/sim-balance.test.mjs | 11 ++++++++++- tools/deck/gen-slaydeck.mjs | 13 ++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tools/balance/sim-balance.mjs b/tools/balance/sim-balance.mjs index f9ec7dd..8ac149e 100644 --- a/tools/balance/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -29,6 +29,13 @@ export function shuffle(arr, rng) { // 공격 피해 공식 — Lua CalcPlayerAttack(힘·약화) + DealDamageToTarget(취약)과 동기화. // floor((base + str) * (weak>0 ? 0.75 : 1)) → floor(... * (vulnOnTarget>0 ? 1.5 : 1)) +// 보상 카드 등급 추첨 (Lua OfferReward 미러) — roll ∈ 1..100, normal 70 / unique 25 / legend 5 +export function rarityForRoll(roll) { + if (roll > 95) return 'legend'; + if (roll > 70) return 'unique'; + return 'normal'; +} + export function calcAttack(base, str, weak, vulnOnTarget) { let dmg = base + str; if (weak > 0) dmg = Math.floor(dmg * 0.75); diff --git a/tools/balance/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs index ce837f0..d9130a1 100644 --- a/tools/balance/sim-balance.test.mjs +++ b/tools/balance/sim-balance.test.mjs @@ -1,9 +1,18 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { - mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack, + mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack, rarityForRoll, } from './sim-balance.mjs'; +test('rarityForRoll: 70/25/5 경계 (Lua OfferReward 미러)', () => { + assert.equal(rarityForRoll(1), 'normal'); + assert.equal(rarityForRoll(70), 'normal'); + assert.equal(rarityForRoll(71), 'unique'); + assert.equal(rarityForRoll(95), 'unique'); + assert.equal(rarityForRoll(96), 'legend'); + assert.equal(rarityForRoll(100), 'legend'); +}); + test('applyDamage: 방어 우선 차감 후 hp', () => { assert.deepEqual(applyDamage(80, 0, 10), { hp: 70, block: 0 }); assert.deepEqual(applyDamage(80, 5, 10), { hp: 75, block: 0 }); diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index cb3754a..d2e9db8 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -3999,9 +3999,20 @@ return pool`, [], 0, 'any'), method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false) self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false) local pool = self:CardPool() +local byRarity = {} +for _, id in ipairs(pool) do + local r = self.Cards[id].rarity or "normal" + if byRarity[r] == nil then byRarity[r] = {} end + table.insert(byRarity[r], id) +end self.RewardChoices = {} for i = 1, 3 do - self.RewardChoices[i] = pool[math.random(1, #pool)] + local roll = math.random(1, 100) + local want = "normal" + if roll > 95 then want = "legend" elseif roll > 70 then want = "unique" end + local bucket = byRarity[want] + if bucket == nil or #bucket == 0 then bucket = pool end + self.RewardChoices[i] = bucket[math.random(1, #bucket)] self:ApplyRewardVisual(i, self.RewardChoices[i]) end local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")