라운드 접수량 버그 수정 + 토큰→코인 용어 통일 + 외부 배포(NAS)

플레이 피드백 4건(주목도 미차감, 상점 빈 상품, 상점 UX 오해, 재화 명칭)을
검토해 방향이 확정된 항목만 반영하고, 외부 테스터에게 링크를 공유할 수 있게
시놀로지 NAS 배포까지 준비했다.

## 게임 로직

- 라운드 접수량 크리티컬 버그 수정: 합본을 반복하면 라운드가 안 끝나던 문제.
  deskCards에서 파생시키던 값을 순수 누적 카운터(roundIntake)로 교체 — 결정
  노트: 2026-09-18-라운드-접수량-누적카운터
- DeskBar/CoreSwipeCard 정보 역할 분리: "이번 라운드 접수량/한도"는 결재
  화면 전담, DeskBar는 책상에 쌓인 것(총 주목도·축별 분포)만 표시
- 토큰 → 코인 용어 일괄 변경: core/ 전체(식별자·CSS·주석·UI)와 기획서 SSOT
  동기화, lucide Coins 아이콘 추가

## 배포

- vite base를 상대 경로로 변경 (Web Station 서브패스 서빙 시 asset 404 방지)
- 모바일 100vh → 100dvh (주소창 노출 시 하단 버튼 가림 방지)
- 빌드 식별자 표시, 타이틀/테마색/description 정리
- deploy.sh + npm run deploy — SSH 키 권한 문제(777 → StrictModes 거부)와
  NAS rsync 게이트를 scp -O로 우회해 실제 배포까지 검증 완료
  → https://play.mint-grape.duckdns.org/projectss/

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
kyoung5seo
2026-09-18 23:20:22 +09:00
co-authored by Claude Sonnet 5
parent 6398599199
commit 20207ff23c
27 changed files with 451 additions and 239 deletions
+19 -23
View File
@@ -14,7 +14,7 @@ import { AXES, AXIS_META, clampGrade } from './symbols.js';
let seq = 0;
// meta: pollutionPool.js에서 뽑은 { name, flavor, tag } — 책상 카드가 실제 이름/사연을 갖게 한다.
// round: 이 카드를 받은 라운드 번호. 한도 판정(roundAttention)이 이월 카드를 빼고 세기 위해 필요하다.
// round: 이 카드를 받은 라운드 번호(기록용 — 한도 판정에는 더 이상 쓰이지 않는다. 아래 참고).
export const makeDeskCard = (axis, grade, meta = {}, round = 1) => ({
id: `desk-${Date.now().toString(36)}-${(seq++).toString(36)}`,
axis,
@@ -53,22 +53,11 @@ export function axisAttention(deskCards, axis) {
}
/** 책상 전체 주목도 = 모든 카드의 등급 합. 이월 카드까지 포함하며, 패시브 페널티
* (게이지 상승 A · 토큰 가산 C)의 기준이다 — 쌓아둔 미결은 계속 대가를 치른다. */
* (게이지 상승 A · 코인 가산 C)의 기준이다 — 쌓아둔 미결은 계속 대가를 치른다. */
export function totalAttention(deskCards) {
return deskCards.reduce((sum, c) => sum + c.grade, 0);
}
/** 이번 라운드 주목도 = 이번 라운드에 새로 받은 카드의 등급 합. 하드 캡(버스트) 판정 전용.
*
* 왜 총 주목도와 분리했나 (2026-09-17): 한도 판정에 책상 전체를 쓰면, 이월된 카드(§4.1) 때문에
* 라운드가 시작하자마자 이미 한도를 넘어 있어 즉시 버스트가 반복된다 — 실제 플레이에서
* 라운드가 계속 자동으로 넘어가는 버그로 드러났다. 블랙잭에서 각 핸드가 0부터 시작하듯
* 한도 판정은 "이번 판에 내가 새로 떠안은 무게"만 센다. 이월 카드의 압박은 사라지지 않고
* 총 주목도(게이지·토큰 가산)로 계속 작용한다. */
export function roundAttention(deskCards, round) {
return deskCards.filter((c) => c.round === round).reduce((sum, c) => sum + c.grade, 0);
}
export function attentionByAxis(deskCards) {
const out = {};
AXES.forEach((axis) => { out[axis] = axisAttention(deskCards, axis); });
@@ -142,7 +131,14 @@ export function applyPassiveGaugeRise(gauges, deskCards, extraFlat = 0) {
* 한도 밑에서는 플레이어가 아무 때나 자율적으로 라운드를 끝내고 정산으로 넘어갈 수 있다 —
* "한 장 더 받을까 말까"의 긴장은 여기서 나온다. 결정 노트: 2026-09-16-라운드-주목도-하드캡-복원.
*
* 2026-09-17: 판정 기준을 총 주목도 → 이번 라운드 주목도로 교체 (roundAttention 주석 참고). */
* 2026-09-18 결정 (누적 카운터로 전환, 결정 노트: 2026-09-18-라운드-접수량-누적카운터):
* 이번 라운드 접수량을 "책상에서 이번 라운드 카드를 필터링해 합산"하는 파생값으로 뒀더니,
* 합본으로 그 카드들이 책상에서 사라지는 순간 접수량도 같이 줄었다. 블랙잭은 낸 카드를
* 버려서 핸드 합계를 낮출 수 없는데, 이 파생 방식은 그걸 가능하게 해서 "합본을 반복하면
* 라운드가 끝나지 않는" 문제를 낳았다(피드백: "합본을 많이 하면 라운드가 너무 길어진다").
* 그래서 접수량은 더 이상 desk.js가 deskCards에서 계산하지 않는다 — CoreGameScreen이
* 카드를 받을 때마다 등급만큼 더하는 **순수 누적 카운터**(라운드가 바뀔 때만 0으로 리셋)로
* 관리하고, 이 모듈의 함수들은 그 값을 인자로 받기만 한다. */
const ROUND_CAP_BASE = 21; // 가안 — 최초 블랙잭 기준으로 복귀
const ROUND_CAP_GROWTH = 5; // 라운드당 가산분, 가안
@@ -152,15 +148,15 @@ export function roundAttentionCap(round, bonus = 0) {
return ROUND_CAP_BASE + (round - 1) * ROUND_CAP_GROWTH + bonus;
}
/** 한도 대비 이번 라운드 주목도 구간: 'normal' | 'warning'(한도 5 이내로 근접) | 'bust'(한도 초과). */
export function attentionZone(deskCards, cap, round) {
const current = roundAttention(deskCards, round);
if (current > cap) return 'bust';
if (current >= cap - 5) return 'warning';
/** 한도 대비 이번 라운드 접수량 구간: 'normal' | 'warning'(한도 5 이내로 근접) | 'bust'(한도 초과).
* intake: CoreGameScreen이 들고 있는 누적 카운터 값 — 합본해도 줄지 않는다. */
export function attentionZone(intake, cap) {
if (intake > cap) return 'bust';
if (intake >= cap - 5) return 'warning';
return 'normal';
}
/* ── C. 토큰 지출 가산 (§4.2-C) — 총 주목도 기준 ── */
/* ── C. 코인 지출 가산 (§4.2-C) — 총 주목도 기준 ── */
const SURCHARGE_TIERS = [
{ max: 20, surcharge: 0 },
@@ -169,10 +165,10 @@ const SURCHARGE_TIERS = [
{ max: Infinity, surcharge: 4 },
];
/** 총 주목도 기준 모든 토큰 지출에 붙는 가산액.
* extraFlat: 활성 규약이 얹는 가산분(council.js의 tokenSurchargeOf) — 이득 규약이면 음수라
/** 총 주목도 기준 모든 코인 지출에 붙는 가산액.
* extraFlat: 활성 규약이 얹는 가산분(council.js의 coinSurchargeOf) — 이득 규약이면 음수라
* 0 밑으로 내려갈 수 있으므로, 지출이 되레 할인되지 않도록 0에서 끊는다. */
export function tokenSurcharge(deskCards, extraFlat = 0) {
export function coinSurcharge(deskCards, extraFlat = 0) {
const total = totalAttention(deskCards);
const tier = SURCHARGE_TIERS.find((t) => total <= t.max);
return Math.max(0, (tier ? tier.surcharge : 4) + extraFlat);