자산(엔진) 시스템 v1 + 오염 카드 압박 강화
자산 시스템 (docs/작업지시서/자산시스템_작업지시서.md 반영, 이전 세션 작업분 커밋): - 발라트로형 조커 레이어 엔진 빌딩 도입. assets.json 8종(트리거→효과→성장 스키마) + engine/assetEngine.js(이벤트 버스, 연쇄 깊이 상한 10)로 기존 수동 클릭형 자산 4종 전면 대체 - AssetInfoPopup 추가 — 자산 슬롯 클릭 시 트리거/효과/스택/충전 상태 표시, 발동 로그 피드 노출 오염 카드 압박 강화 (그레이박스 위기감 피드백 반영): - engine/pollution.js 신설 — 오염 주입 규칙(타입 매핑·등급 추첨·물량)을 단일 모듈로 분리해 사전 예고(SwipeCard) · 실제 주입(GameScreen) · 사후 알림이 같은 계산을 공유하도록 함 - 위험 파라미터를 올리는 결재는 정례회의를 기다리지 않고 그 자리에서 덱에 오염 카드를 주입 - UI 3종: 스와이프 시 오염 유입 사전 경고, 결재 후 유입 토스트, 상단 덱 오염 상시 게이지 - currentCardIndex를 매 결재마다 덱 길이로 정규화(주입/소각으로 덱 길이가 바뀌므로 필요) 기타: - docs/architecture_design.md를 실제 구현(plain JS/CSS, useState, engine/ 순수 함수 경계) 기준으로 전면 재작성 — 기존 문서는 채택되지 않은 초기 제안(Tailwind/Zustand/TS)이었음 - playtest.bat 추가 — 더블클릭으로 dev 서버 실행 (ASCII 전용, chcp로 인한 배치 파싱 오류 회피) - CLAUDE.md 상태 갱신 및 실기 플레이 결과 기록 실기 플레이로 정상 동작 확인. 자산 획득 경로(극비 카드 스와이프)는 플레이 중 한 번도 발생하지 않아 원인 미조사 상태로 남김 — CLAUDE.md에 후속 확인 항목으로 기록. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,105 +1,200 @@
|
||||
# Project SS - 파일 및 컴포넌트 구조 설계
|
||||
# Project SS — 아키텍처 (구현 실태)
|
||||
|
||||
본 문서는 **"서사형 덱빌딩 게임 시스템 기획서"** 및 **"스토리 바이블"**을 바탕으로, 기존 단일 HTML 파일을 모던 웹 프레임워크(React + Vite + Tailwind CSS + Zustand) 기반으로 마이그레이션 및 고도화하기 위한 디렉토리 및 컴포넌트 구조를 정의합니다.
|
||||
> **이 문서는 "제안"이 아니라 "현재 코드가 실제로 어떻게 생겼는가"의 기록이다.**
|
||||
> 2026-07-23 기준으로 실제 구현을 확인해 전면 재작성했다.
|
||||
> 초기 제안서(React + Vite + **Tailwind + Zustand + TypeScript**)는 상당 부분 채택되지 않았고,
|
||||
> 그 내역은 문서 하단 "채택하지 않은 초기 제안"에 남겨 뒀다.
|
||||
>
|
||||
> 게임 **규칙·수치**의 원본은 이 문서가 아니라 `docs/` 하위 기획서들이다. 여기엔 코드 구조만 적는다.
|
||||
|
||||
## 📂 1. 전체 디렉토리 구조 (Directory Structure)
|
||||
---
|
||||
|
||||
## 1. 기술 스택 (실제)
|
||||
|
||||
`client/package.json` 의존성이 전부다. 목록에 없는 것은 쓰지 않는다.
|
||||
|
||||
| 영역 | 채택 | 비고 |
|
||||
|---|---|---|
|
||||
| 프레임워크 | **React 19** | 함수 컴포넌트만, 클래스 컴포넌트 없음 |
|
||||
| 빌드 | **Vite 8** | `client/`가 프로젝트 루트. npm 명령은 `cd client` 후 실행 |
|
||||
| 언어 | **plain JavaScript (`.jsx`)** | TypeScript 미도입 |
|
||||
| 스타일 | **컴포넌트별 개별 `.css`** | 글로벌 클래스명. CSS Modules 아님, Tailwind 아님 |
|
||||
| 상태관리 | **`useState` / `useEffect`** | 외부 상태관리 라이브러리 없음 |
|
||||
| 애니메이션 | **Framer Motion** | 카드 드래그/스와이프 |
|
||||
| 아이콘 | **lucide-react** | |
|
||||
| CSV 파싱 | **papaparse** | 런타임 파싱 (빌드 타임 변환 아님) |
|
||||
| 영구 저장 | **localStorage** | 엔딩 도감 전용 |
|
||||
|
||||
디자인 토큰(컬러/폰트)은 `src/index.css`의 CSS 변수로 정의한다 (`--color-entropy-critical`,
|
||||
`--font-typewriter` 등). 컴포넌트 CSS는 이 변수를 참조한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 디렉토리 구조 (실제)
|
||||
|
||||
```text
|
||||
/src
|
||||
├── assets/ # 이미지, 폰트, 아이콘 등 정적 파일
|
||||
├── components/ # UI 및 도메인 컴포넌트
|
||||
│ ├── common/ # 공통 UI 요소 (버튼, 모달, 툴팁 등)
|
||||
│ ├── dashboard/ # 상단 상태창 및 파라미터 표시
|
||||
│ ├── gameplay/ # 핵심 게임 루프 (카드 스와이프, 자산 관리)
|
||||
│ └── council/ # 주간 의회 시스템 (투표, 법안, 파벌)
|
||||
├── constants/ # 게임 내 상수 (파벌 정보, 테마 컬러, 엔딩 조건 등)
|
||||
├── data/ # 초기 데이터 및 시나리오 (CSV/JSON 형식)
|
||||
├── hooks/ # 커스텀 훅 (ex. useSwipe, useVoting)
|
||||
├── store/ # 전역 상태 관리 (Zustand)
|
||||
├── types/ # TypeScript 타입 정의 (Card, Faction, State 등)
|
||||
├── utils/ # 헬퍼 함수 (엔트로피 계산, 전투/결과 연산 등)
|
||||
├── App.tsx # 루트 컴포넌트 (페이지 라우팅 및 전역 상태 주입)
|
||||
└── main.tsx # 진입점
|
||||
ProjectSS/
|
||||
├── playtest.bat # 더블클릭 → npm install(최초 1회) + dev 서버 + 브라우저 자동 오픈
|
||||
│ # ASCII 전용 — 한글/chcp를 넣으면 cmd.exe가 배치 파싱을 깨뜨린다
|
||||
├── CLAUDE.md # 작업 규칙 · Phase 상태 (실질적인 단일 진실 공급원)
|
||||
├── ROADMAP.md
|
||||
├── docs/ # 기획서 · 스토리 바이블 · 작업지시서 (게임 규칙의 원본)
|
||||
└── client/ # ★ 실제 앱. npm 명령은 여기서 실행
|
||||
├── update_csv.js # ⛔ 보호 파일 — cards.csv 전처리
|
||||
├── update_csv_tags.js # ⛔ 보호 파일 — 내러티브 태그 매핑
|
||||
└── src/
|
||||
├── main.jsx (10줄) 진입점
|
||||
├── App.jsx (13줄) GameScreen 하나만 렌더 — 라우터 없음
|
||||
├── index.css 디자인 토큰(CSS 변수) + 리셋
|
||||
├── assets/ hero.png 등 정적 파일
|
||||
├── components/ ★ flat 구조. 하위 폴더 없음
|
||||
│ ├── GameScreen.jsx (628줄) ★ 상태 허브 + 화면 전환
|
||||
│ ├── SwipeCard.jsx (156줄) 카드 드래그 · 선택지 사전 예고
|
||||
│ ├── CouncilScreen.jsx (220줄) 정례회의(투표 · 개입 액션)
|
||||
│ ├── EndingScreen.jsx (46줄) 엔딩 출력 + 도감 저장
|
||||
│ ├── EndingCodex.jsx (33줄) 엔딩 도감 (localStorage 읽기)
|
||||
│ ├── ChroniclePopup.jsx (63줄) 연대기 + 태그 히스토리
|
||||
│ ├── AssetInfoPopup.jsx (43줄) 자산 상세 · 수동 발동
|
||||
│ └── *.css 컴포넌트당 1개씩 짝을 이룸
|
||||
├── engine/ ★ React를 모르는 순수 로직
|
||||
│ ├── assetEngine.js (226줄) 자산 트리거→효과→연쇄
|
||||
│ └── pollution.js (154줄) 오염 카드 주입 규칙 · 밸런스 상수
|
||||
└── data/
|
||||
├── cards.csv 카드 원본 (런타임 papaparse 파싱)
|
||||
├── assets.json 자산 8종 정의
|
||||
├── narrative_tags.json 재사용 태그 풀 10종
|
||||
├── endings.js (123줄) 엔딩 카탈로그 + 판별 함수
|
||||
└── pollution_cards.json ⚠️ 어디서도 import하지 않음 (레거시)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 2. 상세 컴포넌트 구조 (Components Breakdown)
|
||||
## 3. 핵심 아키텍처 원칙
|
||||
|
||||
### 2.1 공통 UI (`/components/common`)
|
||||
프리미엄한 관료주의 다크/레트로 UI를 구성하는 기본 요소들입니다.
|
||||
- `Button.tsx`: 액션 버튼 (결재, 서명 등 텍스처 효과 포함)
|
||||
- `Modal.tsx`: 미스터리 보드, 법안 확인 등을 위한 공통 팝업
|
||||
- `ProgressBar.tsx`: 게이지 바 (엔트로피, 내부 저항 등)
|
||||
- `AlertToast.tsx`: 패시브 효과 연쇄나 비상 사태를 알리는 시스템 메시지 (우측 하단/상단 노출)
|
||||
### 3.1 상태는 `GameScreen.jsx` 한 곳에 모은다
|
||||
|
||||
### 2.2 대시보드 (`/components/dashboard`)
|
||||
플레이어의 현재 상태(국장실 요약)를 보여줍니다.
|
||||
- `StatusHeader.tsx`: 현재 주차(Week), 일차(Day), 주간 아젠다 노출
|
||||
- `ParameterBoard.tsx`: 3대 핵심 파라미터 게이지 (내부 저항, 엔트로피, 위험 수용 계수)
|
||||
- `ResourcePanel.tsx`: 3대 토큰(예산, 인력, 정보) 및 자원 태그(재력, 무력, 감시) 현황
|
||||
- `FactionDonut.tsx`: 8대 파벌의 의회 지분율 및 우호도를 시각화하는 도넛 차트
|
||||
전역 스토어가 없다. 게임 상태 전부가 `GameScreen`의 `useState`로 존재하고, 하위 컴포넌트는
|
||||
props로 값과 콜백을 받는 프레젠테이션 계층이다.
|
||||
|
||||
### 2.3 게임플레이 (`/components/gameplay`)
|
||||
일일 업무(결재 단계)를 처리하는 핵심 루프 컴포넌트입니다.
|
||||
- `DeskView.tsx`: 국장실 책상 배경 및 전체 레이아웃 래퍼
|
||||
- `CardSwiper.tsx`: 틴더 스타일의 스와이프 인터페이스 (Tinder-like Swipe)
|
||||
- 좌/우 스와이프에 따른 예측 결과(예상 토큰/파라미터 증감)를 오버레이로 표시
|
||||
- `DilemmaCard.tsx`: 개별 딜레마/위기 서류. (제목, 내용, 플레이버 텍스트, 결재/반려 스탬프 이펙트)
|
||||
- `AssetManager.tsx`: 보유 중인 자산(파이프라인, 성질 변환 등 덱 압축 장치) 관리 모달
|
||||
|
||||
### 2.4 의회 시스템 (`/components/council`)
|
||||
매주 5일차 종료 후 진행되는 정치 시스템 컴포넌트입니다.
|
||||
- `CouncilPhase.tsx`: 주간 의회 화면 메인 컨테이너
|
||||
- `BillDisplay.tsx`: 상정된 법안(이득 규약 vs 표적 규약) 비교 UI
|
||||
- `VotingBoard.tsx`: 13명의 임원(상임 이사 8명 + 글로벌 지부장 5명)의 예상 투표 현황 및 게이지 바
|
||||
- `InterventionPanel.tsx`: 국장의 개입 액션 (매수, 장부 조작, 내부 감사) 버튼 및 리스크 안내 패널
|
||||
|
||||
### 2.5 뷰 / 씬 (Views/Scenes)
|
||||
- `LoadingScreen.tsx`: 초기 데이터 파싱 및 게임 세팅 화면
|
||||
- `GameScreen.tsx`: 인게임 전체 메인 화면 (대시보드 + 게임플레이 결합)
|
||||
- `EndingScreen.tsx`: 멀티 엔딩 매트릭스에 따른 최종 결과 출력 (위대한 침묵, 신인류의 탄생 등)
|
||||
|
||||
---
|
||||
|
||||
## 💾 3. 전역 상태 구조 (Global State - Zustand 제안)
|
||||
|
||||
관리가 복잡한 게임 데이터를 효율적으로 다루기 위해 상태를 나눕니다.
|
||||
|
||||
```typescript
|
||||
interface GameState {
|
||||
// 시간/페이즈 관리
|
||||
week: number;
|
||||
day: number;
|
||||
phase: 'DAY_WORK' | 'COUNCIL' | 'ENDING';
|
||||
|
||||
// 핵심 파라미터
|
||||
entropy: number; // 0~100%
|
||||
internalResistance: number; // 0~100%
|
||||
riskTolerance: number; // 0~100%
|
||||
|
||||
// 자원
|
||||
tokens: { budget: number; personnel: number; info: number; };
|
||||
tags: { finance: number; force: number; surveillance: number; };
|
||||
|
||||
// 덱 및 내러티브
|
||||
narrativeTags: string[];
|
||||
ideologyTags: string[];
|
||||
deck: Card[]; // 섞인 오염카드 및 이벤트 풀
|
||||
assets: Asset[]; // 덱 압축 파이프라인
|
||||
|
||||
// 파벌 및 의회 (13명의 임원 정보 포함)
|
||||
factions: FactionState[];
|
||||
currentBills: Bill[];
|
||||
}
|
||||
```
|
||||
GameScreen (상태 소유)
|
||||
├── SwipeCard ← card, onSwipe, phase
|
||||
├── CouncilScreen ← params/tokens/tags + setter, onResolve ※ setter를 직접 넘기는 유일한 예외
|
||||
├── EndingScreen ← ending, narrativeTags
|
||||
├── ChroniclePopup ← chronicle, acquiredTags
|
||||
└── AssetInfoPopup ← instance, def, onActivate
|
||||
```
|
||||
|
||||
**트레이드오프:** 파일이 628줄까지 커졌고 관심사가 섞여 있다. 다만 게임 규칙이 아직 확정되지
|
||||
않아 잦은 수정이 예상되는 그레이박스 단계에서는 "한 파일만 보면 전체 흐름을 안다"는 이점이
|
||||
분할 비용보다 컸다. Phase 5 이후 재검토 대상이다.
|
||||
|
||||
### 3.2 규칙 계산은 `engine/`의 순수 함수로 뺀다
|
||||
|
||||
`engine/`은 React·훅·JSX를 전혀 import하지 않는다. 평범한 객체를 받아 평범한 객체를 돌려준다.
|
||||
|
||||
이 경계 덕분에 **게임을 띄우지 않고 밸런스를 검증할 수 있다.** 실제로 오염 카드 물량 조정 시
|
||||
`node` 스크립트로 `pollution.js`를 직접 import해 600회 시뮬레이션을 돌려 변경 전후를 비교했다.
|
||||
새 규칙 모듈을 만들 때도 이 성질을 깨지 말 것.
|
||||
|
||||
### 3.3 `world` 스냅샷 패턴 (자산 엔진)
|
||||
|
||||
`assetEngine`은 상태를 직접 만지지 않는다. `GameScreen`이 현재 state를 복사한 `world` 객체를
|
||||
만들어 넘기고, 엔진이 변형한 결과를 다시 state로 커밋한다.
|
||||
|
||||
```js
|
||||
const world = buildAssetWorld(); // state 복사 → { cards, tokens, tags, params, owned, quarantine, logs }
|
||||
AssetEngine.onPollutionDraw(world, card.type);
|
||||
AssetEngine.onApprovalTick(world, nextTurns);
|
||||
commitAssetWorld(world); // 결과를 setState로 일괄 반영
|
||||
```
|
||||
|
||||
> **⚠️ 반드시 지킬 것:** 한 이벤트 처리 안에서 `buildAssetWorld`/`commitAssetWorld` 쌍을
|
||||
> **정확히 한 번만** 쓴다. 두 번 짝지어 부르면 두 번째 `build`가 아직 리렌더되지 않은
|
||||
> 낡은 클로저 state를 복사하므로, 나중 커밋이 앞의 변경을 덮어써 버린다.
|
||||
|
||||
엔진 훅 지점은 4곳이다: `onApprovalTick`(결재마다) · `onPollutionDraw`(오염 카드 등장) ·
|
||||
`onSwipeLeft`(좌 스와이프 격리) · `onCouncilEnd`(정례회의 종료). 연쇄 발동은 `fireEvent`가
|
||||
깊이 상한 10으로 처리한다.
|
||||
|
||||
### 3.4 예고 · 실행 · 알림은 같은 함수를 공유한다
|
||||
|
||||
플레이어에게 미리 보여준 숫자와 실제 결과가 어긋나면 정보 자체를 신뢰하지 않게 된다.
|
||||
그래서 오염 주입량은 `pollution.js`의 `previewPollution()` 하나가 단일 기준이다.
|
||||
|
||||
```
|
||||
SwipeCard "☣ 오염 카드 +2" 사전 경고 ─┐
|
||||
GameScreen 실제 덱 주입 ─┼─ 모두 previewPollution() 사용
|
||||
GameScreen "2건 유입" 사후 토스트 ─┘
|
||||
```
|
||||
|
||||
이 함수를 우회해 오염 카드를 만들지 말 것. 밸런스 수치는 `pollution.js` 상단 상수 블록에 모여 있다.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 4. 추천 기술 스택
|
||||
- **Framework**: `React` (컴포넌트 분리를 통한 복잡한 UI 관리)
|
||||
- **Build Tool**: `Vite` (빠른 HMR 및 빌드 속도)
|
||||
- **Styling**: `Tailwind CSS` (유틸리티 클래스로 빠른 UI 구현) + CSS Animations (스와이프 액션, 팝업 등)
|
||||
- **State Management**: `Zustand` (가볍고 직관적인 전역 상태 관리)
|
||||
- **Interactions**: `Framer Motion` 또는 `react-spring` (부드러운 카드 스와이프 액션 및 몰입감 있는 애니메이션)
|
||||
- **Type Checking**: `TypeScript` (복잡한 자원 계산과 이벤트 분기에 대한 안정성 보장)
|
||||
## 4. 데이터 흐름
|
||||
|
||||
```
|
||||
cards.csv ──(?raw import)──> papaparse ──> buildCardFromRow() ──> 덱 배열
|
||||
│
|
||||
narrative_tags.json ──> left_tag/right_tag id로 조인 ────────────────┘
|
||||
```
|
||||
|
||||
- **카드 데이터는 런타임에 파싱한다.** `import cardsCsvRaw from '../data/cards.csv?raw'` 후
|
||||
브라우저에서 papaparse로 처리한다. 빌드 타임 변환 단계가 없으므로 CSV를 고치면 새로고침만으로 반영된다.
|
||||
- 덱은 **인덱스를 순환(modulo)하는 풀**이다. 소진되어 없어지지 않고 계속 돈다.
|
||||
`currentCardIndex`는 매 결재마다 최종 덱 길이로 정규화해 범위 안에 유지한다 — 주입/소각으로
|
||||
길이가 변하기 때문이다.
|
||||
- 카드 풀은 페이즈별로 분리한다: 페이즈1(일상/위기)로 시작하고, 내러티브 태그 3종 획득 시
|
||||
페이즈2(작전/극비) 풀이 덱에 삽입된다.
|
||||
- 오염 카드(신화/조직저항/사회공황/이사회압박)는 `contaminationTemplates`로 따로 보관하다가
|
||||
주입 시점에 복제해 고유 id를 붙인다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 화면 전환
|
||||
|
||||
라우터가 없다. `GameScreen`이 상태를 보고 조기 return으로 화면을 고른다.
|
||||
|
||||
```js
|
||||
if (gameOver) return <EndingScreen ... />;
|
||||
if (showCouncil) return <CouncilScreen ... />;
|
||||
if (cards.length === 0) return <div>데이터를 불러오는 중...</div>;
|
||||
return ( /* 결재 화면 */ );
|
||||
```
|
||||
|
||||
팝업(연대기·자산 정보·도감)은 화면을 갈아끼우지 않고 조건부 오버레이로 렌더한다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 영구 저장
|
||||
|
||||
localStorage에 저장하는 것은 **엔딩 도감뿐이다.** 세이브/로드 기능은 없고, 게임오버 시
|
||||
`window.location.reload()`로 재시작한다.
|
||||
|
||||
- 키: `ss_endings_v2` (`EndingCodex.jsx`에서 `ENDINGS_STORAGE_KEY`로 export)
|
||||
- 값: 해금된 엔딩 **id** 배열. 구버전 `ss_endings`는 제목 문자열 기반이라 폐기했고 호환되지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 채택하지 않은 초기 제안
|
||||
|
||||
초기 제안서에 있었으나 **의도적으로 도입하지 않은** 것들이다. 되살리려면 CLAUDE.md 규칙부터 바꿔야 한다.
|
||||
|
||||
| 제안 | 현재 | 사유 |
|
||||
|---|---|---|
|
||||
| Tailwind CSS | 컴포넌트별 `.css` | 관료제 다크 UI는 유틸리티 클래스보다 커스텀 CSS가 손에 맞았다 |
|
||||
| Zustand | `useState` | 상태가 한 컴포넌트에 모여 있어 전역 스토어의 값어치가 없다. **도입 금지** |
|
||||
| TypeScript | plain JS | 그레이박스 단계의 잦은 스키마 변경 대비 타입 유지비가 컸다 |
|
||||
| `store/ hooks/ types/ utils/ constants/` | 없음 | 규칙 로직은 `engine/`으로 통합 |
|
||||
| `components/{common,dashboard,gameplay,council}/` | flat | 컴포넌트 7개뿐이라 계층이 불필요 |
|
||||
| 8대 파벌 도넛 차트 | 텍스트 목록 | 파벌은 12종 풀에서 5명 선발 방식으로 변경됨 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 알려진 부채
|
||||
|
||||
- **`GameScreen.jsx` 628줄** — 상태 허브 + 화면 전환 + 스와이프 처리 + 파벌 계산이 한 파일에 있다.
|
||||
- **`data/pollution_cards.json` 미사용** — 어디서도 import하지 않는 레거시. 오염 카드는 `cards.csv`에서 온다.
|
||||
- **테스트 코드 없음** — 검증은 `engine/` 모듈을 직접 부르는 일회성 node 스크립트로 해 왔다.
|
||||
- **`window.location.reload()` 재시작** — 상태 초기화 로직이 따로 없다.
|
||||
|
||||
128
docs/작업지시서/자산시스템_작업지시서.md
Normal file
128
docs/작업지시서/자산시스템_작업지시서.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 자산(엔진) 시스템 작업지시서 — 그레이박스 v1
|
||||
|
||||
> **목적**: 발라트로형 엔진 빌딩 검증. 자산 8종을 데이터로 구현하고, 자산 정보 UI 블로커를 해결하여 실제 플레이 테스트가 가능한 상태를 만든다.
|
||||
> **원칙**: Greybox First. 수치는 전부 가안이며 튜닝 대상. 비주얼 연출 금지(텍스트/기본 도형만).
|
||||
> **주의**: `update_csv_tags.js`, `update_csv.js` 등 파이프라인 스크립트는 수정 금지. 본 작업은 신규 데이터 파일 + 신규 로직 모듈로만 구성한다.
|
||||
|
||||
---
|
||||
|
||||
## Part A. 자산 데이터 스키마
|
||||
|
||||
신규 파일 `src/data/assets.json` 생성. 자산 1장의 문법은 **트리거 → 효과 → (선택) 성장**.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "asset_purge_001",
|
||||
"name": "문서 파쇄실",
|
||||
"grade": "C", // C | B | A | S
|
||||
"archetype": "purge", // purge | convert | quarantine | skip
|
||||
"trigger": {
|
||||
"type": "every_n_approvals", // 트리거 enum (아래 표)
|
||||
"params": { "n": 5 }
|
||||
},
|
||||
"effect": {
|
||||
"type": "purge_lowest", // 효과 enum (아래 표)
|
||||
"params": { "count": 1 }
|
||||
},
|
||||
"growth": null, // A급 이상만 사용. { "stackOn": "...", "per": 3, "bonus": {...} }
|
||||
"risk": null, // 부작용. { "param": "risk_tolerance", "amount": 5, "when": "council_end" }
|
||||
"faction_link": "scavengers", // 우호도 연동 파벌 (nullable)
|
||||
"description": "결재 5건마다 덱에서 최하 등급 오염 카드 1장을 영구 소각한다.",
|
||||
"flavor": "파쇄 용량: 일일 1건. 증설 요청은 3회 반려됨."
|
||||
}
|
||||
```
|
||||
|
||||
### 트리거 enum (v1 범위)
|
||||
|
||||
| type | 의미 | params |
|
||||
|---|---|---|
|
||||
| `on_pollution_draw` | 오염 카드 드로우 시 | `category`(신화/저항/공황/압박, nullable), `minGrade`(nullable) |
|
||||
| `on_swipe_left` | 좌 스와이프 시 | — |
|
||||
| `on_token_spend` | 토큰 소모 시 | `tokenType`(nullable) |
|
||||
| `every_n_approvals` | 결재 N건마다 | `n` |
|
||||
| `on_council_end` | 의회 종료 시 | — |
|
||||
| `on_purge` | 오염 카드 소각 시 (연쇄용) | — |
|
||||
| `on_convert` | 오염 카드 변환 시 (연쇄용) | — |
|
||||
| `on_quarantine_fail` | 격리 실패 복귀 시 (연쇄용) | — |
|
||||
| `manual_charge` | 충전식 수동/자동 방패 | `chargeEvery` |
|
||||
|
||||
### 효과 enum (v1 범위)
|
||||
|
||||
| type | 의미 | params |
|
||||
|---|---|---|
|
||||
| `purge_lowest` | 최하 등급 오염 소각 | `count` |
|
||||
| `convert_to_token` | 오염 → 토큰 환전 | `tokenType`, `amount` |
|
||||
| `quarantine` | 오염 격리 (조건 실패 시 강화 복귀) | `failCondition` |
|
||||
| `negate_penalty` | 페널티 1회 무효 | `category`(nullable) |
|
||||
| `gain_token` | 토큰 생성 | `tokenType`, `amount` |
|
||||
| `gain_tag` | 자원 태그 생성 | `tagType`, `amount` |
|
||||
| `convert_multiplier` | 변환 산출 배수 (상시) | `multiplier` |
|
||||
| `quarantine_fail_to_purge` | 격리 실패 → 영구 소각으로 치환 (규칙 왜곡) | `bonusTag`(nullable) |
|
||||
|
||||
> 구현 노트: 트리거는 게임 이벤트 버스에 리스너로 등록. `on_purge`/`on_convert`/`on_quarantine_fail`은 다른 자산의 효과가 발생시키는 이벤트를 구독하므로, **효과 실행부가 반드시 이벤트를 발행**해야 연쇄가 성립한다. 무한 루프 방지를 위해 결재 1건당 연쇄 깊이 상한 10회.
|
||||
|
||||
---
|
||||
|
||||
## Part B. 자산 8종 데이터 (확정 배치 1차)
|
||||
|
||||
### ① 소각 특화 (faction: scavengers)
|
||||
|
||||
1. **문서 파쇄실** — C급
|
||||
- 트리거: `every_n_approvals(5)` / 효과: `purge_lowest(1)`
|
||||
2. **소각 처리 대장** — A급
|
||||
- 트리거: `on_purge` / 성장: 스택 +1, 스택 3마다 `purge_lowest(1)` 추가 발동
|
||||
|
||||
### ② 변환 경제 (faction: nexus_traders)
|
||||
|
||||
3. **폐기물 재활용 규정** — C급
|
||||
- 트리거: `on_pollution_draw(category: 신화)` / 효과: `gain_token(예산, 2)` (페널티는 정상 적용 후)
|
||||
4. **부산물 매각 계약** — B급
|
||||
- 효과: `convert_multiplier(2)` 상시 / 리스크: 의회 종료 시마다 위험 수용 계수 +5%
|
||||
|
||||
### ③ 격리 도박 (faction: occult_collectors)
|
||||
|
||||
5. **임시 격리 컨테이너** — C급
|
||||
- 트리거: `on_swipe_left`(발동 선택 가능) / 효과: `quarantine(failCondition: 다음 의회까지 대중 공황 1단계 상승)`
|
||||
6. **심연 보관 조례** — S급
|
||||
- 트리거: `on_quarantine_fail` / 효과: `quarantine_fail_to_purge(bonusTag: 감시 +1)`
|
||||
|
||||
### ④ 무효 회피 (faction: field_agents_union — 우호도 **하락** 연동)
|
||||
|
||||
7. **결재 반려 도장** — C급
|
||||
- 트리거: `manual_charge(chargeEvery: 10)` / 효과: `negate_penalty(전체)`
|
||||
8. **책임 소재 불명 처리** — B급
|
||||
- 트리거: 조직 저항 계열 `negate_penalty` 발동 시 / 효과: 인력 토큰 소모 없이 통과 / 리스크: 내부 저항 +3%
|
||||
|
||||
> 수치(5건, 토큰 2, 스택 3, 5%, 3% 등)는 전부 가안. 플레이 테스트 후 일괄 튜닝.
|
||||
|
||||
---
|
||||
|
||||
## Part C. UX 블로커 해결 (최우선 선행 작업)
|
||||
|
||||
**현재 문제**: 자산 클릭 시 아무 정보도 표시되지 않음 → 덱압축 시스템 평가 자체가 불가능.
|
||||
|
||||
1. 하단 자산 슬롯의 `Icon_Asset` 탭/클릭 시 정보 팝업 표시:
|
||||
- 이름, 등급, 아키타입 아이콘
|
||||
- **트리거 조건 → 효과** 를 한 문장으로 (description 필드 그대로)
|
||||
- 성장형이면 현재 스택 수치 표시
|
||||
2. **자산 발동 시 가시 피드백 (텍스트 로그로 충분)**: 화면 한쪽에 "『문서 파쇄실』 발동 — [하급 신화] 소각됨" 식의 1줄 로그. 발라트로의 조커 점멸에 해당하는 최소 구현. 연쇄 발동 시 순서대로 줄줄이 출력되어야 "엔진이 돌아간다"는 감각이 생긴다.
|
||||
3. 그레이박스 기준이므로 애니메이션 불필요. 텍스트 로그 + 팝업만.
|
||||
|
||||
---
|
||||
|
||||
## Part D. 플레이 테스트 체크리스트
|
||||
|
||||
구현 완료 후 아래를 검증한다.
|
||||
|
||||
- [ ] **역전 엔진 성립**: 3번(재활용 규정) + 1번(파쇄실) 조합 시, 오염이 늘수록 자원이 돌고 덱이 정화되는 체감이 있는가?
|
||||
- [ ] **연쇄 가시성**: 2번(소각 처리 대장) 보유 시 소각→스택→추가 소각 연쇄가 로그로 읽히는가?
|
||||
- [ ] **S급 킹핀**: 6번(심연 보관 조례) 획득 전후로 격리 자산의 가치 평가가 뒤집히는가? (획득 전: 격리 기피 / 획득 후: 격리 남발)
|
||||
- [ ] **무효 빌드의 풍자 체감**: 7+8번 조합으로 "덱은 더럽지만 아무 일도 안 일어나는" 상태가 만들어지는가?
|
||||
- [ ] **오버파워 확인**: 페이즈 3에서 엔진이 오염 주입 속도를 이기는 순간이 오는가? (이겨야 정상 — 발라트로 모델)
|
||||
- [ ] 연쇄 깊이 상한(10회)에 걸리는 케이스가 있는가?
|
||||
|
||||
## 범위 외 (다음 배치)
|
||||
|
||||
- 의회 압박 강화(안건 예고제, 표차 가시화) — 별도 작업지시서로 분리 예정
|
||||
- 하이브리드 아키타입 자산 및 A/S급 보강 (2차 배치)
|
||||
- 자산 획득 경로(극비 프로젝트 보상 테이블) 튜닝
|
||||
Reference in New Issue
Block a user