From a38d7ade28b5a4dc5a37464c3794b4345bb7e463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=A0=95=EB=AF=BC?= Date: Thu, 23 Jul 2026 14:37:09 +0900 Subject: [PATCH] Switch streak bonus to capped per-day ramp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스트릭 보너스를 마일스톤 계단(+2/+5/+10/+20)에서 min(스트릭 일수, 15) 일별 점증 구조로 교체 - 적중일 기본 적립 대비 보너스 비중을 30~50%까지 높여 스트릭 유지 유인 강화 (포인트 경제 리서치 결론 반영) - 상한 15로 시즌 이월 장기 스트릭의 이득을 초반 램프업 생략(~105pt) 수준으로 제한 — 이월은 의도적 허용 - judgmentService 테스트의 포인트 기대값을 새 공식으로 갱신 --- src/constants/judgment.ts | 17 +++++++++++------ tests/services/judgmentService.test.ts | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/constants/judgment.ts b/src/constants/judgment.ts index a9ff2ec..5b24a2e 100644 --- a/src/constants/judgment.ts +++ b/src/constants/judgment.ts @@ -33,14 +33,19 @@ export function judgeByCounts(correct: number, t: Thresholds): DailyJudgment { return "fail"; } +/** + * 스트릭 유지·증가 시 지급되는 보너스 포인트 상한. + * 상한이 있어야 시즌을 넘겨 이월된 장기 스트릭의 이득이 + * "초반 램프업 생략" 수준(최대 ~105pt)으로 제한된다 — 이월은 의도적으로 허용. + */ +export const STREAK_BONUS_CAP = 15; + /** * 스트릭 유지·증가 시 지급되는 보너스 포인트. - * 스트릭이 길수록 가산폭이 커지도록 완만한 계단 함수. + * 스트릭 일수만큼 매일 가산하되 [STREAK_BONUS_CAP]에서 멈추는 일별 점증 구조 — + * 기본 적립(적중 수 × 10) 대비 30~50%까지 커져 스트릭을 지킬 유인을 만든다. */ export function streakBonus(streakAfter: number): number { - if (streakAfter >= 30) return 20; - if (streakAfter >= 14) return 10; - if (streakAfter >= 7) return 5; - if (streakAfter >= 3) return 2; - return 0; + if (streakAfter <= 0) return 0; + return Math.min(streakAfter, STREAK_BONUS_CAP); } diff --git a/tests/services/judgmentService.test.ts b/tests/services/judgmentService.test.ts index 4f1c05d..9bb5e38 100644 --- a/tests/services/judgmentService.test.ts +++ b/tests/services/judgmentService.test.ts @@ -135,7 +135,7 @@ describe("judgmentService (Firestore emulator)", () => { expect(hist.streakAfter).toBe(1); expect(user.currentStreak).toBe(1); expect(user.highestStreak).toBe(1); - expect(user.tierPoints).toBe(50); // 5 * 10, streak 1이라 보너스 0 + expect(user.tierPoints).toBe(51); // 5 * 10 + 스트릭 보너스 min(1, 15) expect(user.lastJudgedDate).toBe(TUE); }); @@ -157,7 +157,7 @@ describe("judgmentService (Firestore emulator)", () => { expect(hist.judgment).toBe("success"); expect(user.currentStreak).toBe(1); - expect(user.tierPoints).toBe(30); + expect(user.tierPoints).toBe(31); // 3 * 10 + 스트릭 보너스 1 }); it("5경기·2적중은 fail, 스트릭 0 유지, 포인트 불변", async () => { @@ -382,7 +382,7 @@ describe("judgmentService (Firestore emulator)", () => { const user = await readUser(); expect(user.currentStreak).toBe(1); - expect(user.tierPoints).toBe(50); + expect(user.tierPoints).toBe(51); }); });