From 20207ff23ca110302059e9e18d89142b986de0c2 Mon Sep 17 00:00:00 2001 From: kyoung5seo Date: Fri, 18 Sep 2026 23:20:22 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9A=B4=EB=93=9C=20=EC=A0=91?= =?UTF-8?q?=EC=88=98=EB=9F=89=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?+=20=ED=86=A0=ED=81=B0=E2=86=92=EC=BD=94=EC=9D=B8=20=EC=9A=A9?= =?UTF-8?q?=EC=96=B4=20=ED=86=B5=EC=9D=BC=20+=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC(NAS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 플레이 피드백 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 --- client/index.html | 9 +- client/package.json | 4 +- client/src/core/components/CombinePanel.css | 6 +- client/src/core/components/CombinePanel.jsx | 6 +- client/src/core/components/CoreGameScreen.css | 8 ++ client/src/core/components/CoreGameScreen.jsx | 83 +++++++++++-------- client/src/core/components/CoreSwipeCard.jsx | 14 ++-- client/src/core/components/CouncilScreen.css | 24 +++--- client/src/core/components/CouncilScreen.jsx | 25 +++--- client/src/core/components/DeskBar.css | 28 +------ client/src/core/components/DeskBar.jsx | 39 +++------ client/src/core/components/HandGuidePopup.css | 4 +- client/src/core/components/HandGuidePopup.jsx | 2 +- client/src/core/components/InfoTooltip.css | 4 +- client/src/core/components/ShopScreen.css | 4 +- client/src/core/components/ShopScreen.jsx | 11 +-- client/src/core/engine/council.js | 46 +++++----- client/src/core/engine/desk.js | 42 +++++----- client/src/core/engine/hands.js | 10 +-- client/src/core/engine/shop.js | 8 +- client/src/core/engine/symbols.js | 4 +- client/src/index.css | 19 ++++- client/vite.config.js | 24 ++++++ deploy.sh | 83 +++++++++++++++++++ docs/DEVLOG.md | 80 +++++++++++++++++- docs/ProjectSS_코어시스템_기획서.md | 78 ++++++++--------- .../2026-09-18-라운드-접수량-누적카운터.md | 25 ++++++ 27 files changed, 451 insertions(+), 239 deletions(-) create mode 100755 deploy.sh create mode 100644 docs/decisions/2026-09-18-라운드-접수량-누적카운터.md diff --git a/client/index.html b/client/index.html index 87e20c9..df5efdd 100644 --- a/client/index.html +++ b/client/index.html @@ -2,9 +2,14 @@ - + + - projectss + + + + Project SS — 종말을 결재하시겠습니까?
diff --git a/client/package.json b/client/package.json index e58c87c..fbe4e6b 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,9 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "deploy": "bash ../deploy.sh", + "deploy:dry": "bash ../deploy.sh --dry-run" }, "dependencies": { "framer-motion": "^12.40.0", diff --git a/client/src/core/components/CombinePanel.css b/client/src/core/components/CombinePanel.css index de6338f..ac85c07 100644 --- a/client/src/core/components/CombinePanel.css +++ b/client/src/core/components/CombinePanel.css @@ -104,7 +104,7 @@ .combine-card.selected { opacity: 1; - outline: 2px solid var(--color-token-budget); + outline: 2px solid var(--color-coin); border-radius: 6px; } @@ -148,7 +148,7 @@ } .combine-match:hover { - border-color: var(--color-token-budget); + border-color: var(--color-coin); } .match-top { @@ -162,7 +162,7 @@ font-weight: 700; } -.match-score { color: var(--color-token-budget); } +.match-score { color: var(--color-coin); } .match-detail { font-size: 11px; diff --git a/client/src/core/components/CombinePanel.jsx b/client/src/core/components/CombinePanel.jsx index ffe00df..9fb2bb6 100644 --- a/client/src/core/components/CombinePanel.jsx +++ b/client/src/core/components/CombinePanel.jsx @@ -10,7 +10,7 @@ import './CombinePanel.css'; // 명분 종류를 고르기 위해 일부러 하위 족보를 쓰는 선택이 존재해야 하기 때문이다. // // 2026-09-15 결정 반영: 선택은 최대 5장(§5.1.1). 족보에 실제로 쓰이는 카드만 소멸하므로, -// 각 족보 후보가 어떤 카드를 가져가고 토큰이 얼마 나오는지(등급 합계, §8.2)를 미리 보여준다. +// 각 족보 후보가 어떤 카드를 가져가고 코인이 얼마 나오는지(등급 합계, §8.2)를 미리 보여준다. export default function CombinePanel({ deskCards, onConfirm, onClose, activeProtocols = [] }) { const [selectedIds, setSelectedIds] = useState([]); @@ -93,7 +93,7 @@ export default function CombinePanel({ deskCards, onConfirm, onClose, activeProt {matches.map((patternId) => { const preview = resolveCombine(selection, patternId); if (!preview) return null; - const { pattern, usedCards, tokens, justification, performanceScore } = preview; + const { pattern, usedCards, coins, justification, performanceScore } = preview; return ( - {tokensPaid} - - 납입 1개 = 실적 5 +
+ 코인 {coins - coinsPaid} 보유 + + {coinsPaid} + + 납입 1개 = 실적 5
diff --git a/client/src/core/components/DeskBar.css b/client/src/core/components/DeskBar.css index a52ac07..3369c23 100644 --- a/client/src/core/components/DeskBar.css +++ b/client/src/core/components/DeskBar.css @@ -8,10 +8,6 @@ font-family: var(--font-typewriter); } -/* 경고/한도초과(bust) 구간을 테두리로 알린다 (§4.2-B) — bust는 실제로 라운드를 강제 종료시킨다. */ -.desk-bar.zone-warning { border-bottom-color: var(--color-risk-critical); } -.desk-bar.zone-bust { border-bottom-color: var(--color-entropy-critical); } - .desk-bar-top { display: flex; align-items: center; @@ -29,24 +25,8 @@ font-size: 14px; } -.zone-warning .desk-attention { color: var(--color-risk-critical); } -.zone-bust .desk-attention { color: var(--color-entropy-critical); } - .desk-surcharge { color: var(--color-risk-critical); } -.desk-tokens { opacity: 0.8; } - -.desk-zone-banner { - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - background: rgba(255, 215, 0, 0.1); - color: var(--color-risk-critical); -} - -.zone-bust .desk-zone-banner { - background: rgba(139, 0, 0, 0.18); - color: #ff8a80; -} +.desk-coins { opacity: 0.8; } .desk-bar-actions { display: flex; @@ -71,7 +51,7 @@ } .desk-combine-btn { - background: var(--color-token-budget); + background: var(--color-coin); color: #0a0a0a; border-color: transparent; } @@ -167,7 +147,3 @@ opacity: 1; } -.desk-carried { - opacity: 0.55; - font-size: 11px; -} diff --git a/client/src/core/components/DeskBar.jsx b/client/src/core/components/DeskBar.jsx index fe46ffb..49fd5b3 100644 --- a/client/src/core/components/DeskBar.jsx +++ b/client/src/core/components/DeskBar.jsx @@ -1,53 +1,40 @@ import React, { useState } from 'react'; +import { Coins } from 'lucide-react'; import { AXES, AXIS_META } from '../engine/symbols'; import { - totalCount, totalAttention, roundAttention, attentionByAxis, attentionZone, tokenSurcharge, - gaugeRiseRateByAxis, + totalCount, totalAttention, attentionByAxis, coinSurcharge, gaugeRiseRateByAxis, } from '../engine/desk'; import { AxisIcon } from './SymbolBadge'; import DeskDetailPopup from './DeskDetailPopup'; import './DeskBar.css'; // 책상 상태 상시 노출 (§3.4) — "이 표시가 없으면 합본 설계가 불가능하며 스와이프는 무의미한 도박이 된다." -// 2026-09-16 결정 반영: 페널티 기준이 등급 합계(주목도)이고, 이제 주목도가 **이번 라운드의 -// 한도(attentionCap)를 넘으면 그 자리에서 라운드가 강제 종료**된다(§4.2-B, 결정 노트 -// 2026-09-16-라운드-주목도-하드캡-복원). 그래서 총 주목도뿐 아니라 한도까지 항상 보여야 -// "한 장 더 받을지, 지금 정산할지" 판단이 선다. +// 2026-09-16 결정 반영: 페널티 기준이 등급 합계(주목도)다. // -// 2026-09-17 수정: (1) "정산하기" 버튼은 결재 카드 아래(CoreSwipeCard)로 옮겼다 — 툴바 구석에 -// 있으면 핵심 결정 버튼인 걸 모른다는 피드백. (2) 한도 판정에 쓰는 주목도는 **이번 라운드분** -// 이다. 책상 전체 주목도는 이월 카드까지 포함해 게이지·토큰 가산에 계속 작용하므로 둘 다 보여준다. +// 2026-09-18 결정(역할 분리): 이번 라운드 접수량/한도("카드를 더 받을지" 판단)는 결재 화면 +// (CoreSwipeCard)이 전담한다. 여기 DeskBar는 **책상에 지금 쌓여 있는 것**(이월 포함 총 +// 주목도, 축별 분포, 게이지·지출 가산)만 보여준다 — "무엇을 합본할 수 있는가"에 필요한 정보다. const MAX_GRADE_CHIPS = 6; export default function DeskBar({ - deskCards, gauges, tokens, round, attentionCap, onOpenCombine, onOpenGuide, - gaugeExtraFlat = 0, tokenExtraFlat = 0, + deskCards, gauges, coins, onOpenCombine, onOpenGuide, + gaugeExtraFlat = 0, coinExtraFlat = 0, }) { const [detailAxis, setDetailAxis] = useState(null); const total = totalCount(deskCards); const deskAttention = totalAttention(deskCards); - const current = roundAttention(deskCards, round); - const carried = deskAttention - current; const perAxis = attentionByAxis(deskCards); const rates = gaugeRiseRateByAxis(deskCards, gaugeExtraFlat); - const surcharge = tokenSurcharge(deskCards, tokenExtraFlat); - const zone = attentionZone(deskCards, attentionCap, round); - - const zoneLabel = { - normal: null, - warning: `한도 근접 (${attentionCap - 5}~${attentionCap}) — 정산할지 고려하십시오`, - bust: `한도 초과! (${attentionCap}+) — 이번 라운드는 강제 종료됩니다`, - }[zone]; + const surcharge = coinSurcharge(deskCards, coinExtraFlat); return ( -
+
- 책상 {total}장 · 이번 라운드 {current}/{attentionCap} - {carried > 0 && (이월 {carried})} + 책상 {total}장 · 주목도 {deskAttention} {surcharge > 0 && · 지출 가산 +{surcharge}} - · 토큰 {tokens} + · {coins}
@@ -57,8 +44,6 @@ export default function DeskBar({
- {zoneLabel &&
{zoneLabel}
} -
{AXES.map((axis) => { const cards = deskCards.filter((c) => c.axis === axis); diff --git a/client/src/core/components/HandGuidePopup.css b/client/src/core/components/HandGuidePopup.css index 23640f1..2de7253 100644 --- a/client/src/core/components/HandGuidePopup.css +++ b/client/src/core/components/HandGuidePopup.css @@ -47,7 +47,7 @@ } .guide-hint strong { - color: var(--color-token-budget); + color: var(--color-coin); } .guide-list { @@ -75,7 +75,7 @@ flex: 1; } -.guide-score { font-size: 12px; color: var(--color-token-budget); } +.guide-score { font-size: 12px; color: var(--color-coin); } .guide-justification { font-size: 12px; opacity: 0.65; } .guide-condition { diff --git a/client/src/core/components/HandGuidePopup.jsx b/client/src/core/components/HandGuidePopup.jsx index 07522d1..9622ab2 100644 --- a/client/src/core/components/HandGuidePopup.jsx +++ b/client/src/core/components/HandGuidePopup.jsx @@ -34,7 +34,7 @@ export default function HandGuidePopup({ onClose }) { "같은 등급"은 트럼프의 같은 숫자, "같은 축"은 같은 무늬와 같다. 한 번에 {' '}{MIN_COMBINE_CARDS}~{MAX_COMBINE_CARDS}장을 선택하면 성립하는 족보가 자동 판정되며, 족보에 실제로 쓰인 카드만 소멸한다 (나머지는 책상에 남는다). - 토큰은 사용된 카드의 등급 합계만큼 나오고, 등급 {TOP_SECRET_THRESHOLD} 이상 + 코인은 사용된 카드의 등급 합계만큼 나오고, 등급 {TOP_SECRET_THRESHOLD} 이상 카드가 끼면 명분이 +1 된다.

diff --git a/client/src/core/components/InfoTooltip.css b/client/src/core/components/InfoTooltip.css index 626813e..6e51242 100644 --- a/client/src/core/components/InfoTooltip.css +++ b/client/src/core/components/InfoTooltip.css @@ -33,7 +33,7 @@ .info-tip-trigger.tone-good { background: rgba(76, 175, 80, 0.12); border-color: rgba(76, 175, 80, 0.4); - color: var(--color-token-budget); + color: var(--color-coin); } /* 물음표 배지 — 누르면 설명이 나온다는 신호. 이게 없으면 툴팁의 존재 자체를 모른다. */ @@ -101,7 +101,7 @@ } .tone-bad .info-tip-summary { color: var(--symbol-threat, #ff7b6b); } -.tone-good .info-tip-summary { color: var(--color-token-budget); } +.tone-good .info-tip-summary { color: var(--color-coin); } .info-tip-detail { font-size: 11px; diff --git a/client/src/core/components/ShopScreen.css b/client/src/core/components/ShopScreen.css index 54cf643..05ffad9 100644 --- a/client/src/core/components/ShopScreen.css +++ b/client/src/core/components/ShopScreen.css @@ -22,7 +22,7 @@ line-height: 1.5; } -.shop-tokens { +.shop-coins { font-family: var(--font-typewriter); font-size: 13px; padding: 8px 12px; @@ -61,7 +61,7 @@ .shop-buy-btn { font-family: var(--font-typewriter); font-size: 11px; - background: var(--color-token-budget); + background: var(--color-coin); color: #0a0a0a; border: none; border-radius: 4px; diff --git a/client/src/core/components/ShopScreen.jsx b/client/src/core/components/ShopScreen.jsx index 875acc1..0f711d5 100644 --- a/client/src/core/components/ShopScreen.jsx +++ b/client/src/core/components/ShopScreen.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Coins } from 'lucide-react'; import SymbolBadge from './SymbolBadge'; import { purgePrice, REROLL_COST, canAfford } from '../engine/shop'; import './ShopScreen.css'; @@ -6,18 +7,18 @@ import './ShopScreen.css'; // 상점 (§8.3) — 라운드 종료 후 의회 진입 전 정산 단계. 자산 구매·소모품·전례 매입은 // 각각 §10·§12·§11(3차 범위)이 있어야 성립해서 아직 없다. 지금 성립하는 품목은 // "오염 카드 직접 처리" 하나뿐이다 — 족보가 안 맞을 때의 탈출구, 명분·실적은 안 준다. -export default function ShopScreen({ display, tokens, deskSurcharge, onPurge, onReroll, rerolled, onContinue }) { +export default function ShopScreen({ display, coins, deskSurcharge, onPurge, onReroll, rerolled, onContinue }) { return (

정산 — 상점

- 족보가 안 맞는 오염 카드를 토큰으로 직접 처리할 수 있습니다 (명분·실적은 없음). + 족보가 안 맞는 오염 카드를 코인으로 직접 처리할 수 있습니다 (명분·실적은 없음). 가격은 등급만큼이며 지출 가산 +{deskSurcharge}이 얹힙니다.

-
보유 토큰 {tokens}
+
보유 코인 {coins}
{display.length === 0 &&

진열할 오염 카드가 없습니다.

} @@ -29,7 +30,7 @@ export default function ShopScreen({ display, tokens, deskSurcharge, onPurge, on {card.name || '미상 서류'}