docs(D): 데이터 외부화 설계·구현 계획 문서

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 01:25:25 +09:00
parent 8789330a4e
commit 929347c599
2 changed files with 430 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
# 카드/적 데이터 외부화 (TODO 항목 D) — 설계
> 작성: 2026-06-09 / 상태: 승인됨 / 근거: TODO.md 항목 D + gen-slaydeck.mjs 분석.
> 선행: B(전투 통합)·A(문서 정합) 완료. F(밸런스 시뮬레이터)의 선행 조건.
## 문제
카드 정의(`self.Cards`)·시작 덱·적 정의(이름/HP/의도)가 `gen-slaydeck.mjs``StartCombat`
Lua 문자열에 하드코딩돼 있다. 카드/적 추가·밸런싱이 생성기 코드 수정을 요구한다.
## 목표
카드·적 데이터를 외부 JSON으로 분리하고, 생성기가 읽어 codeblock·UI에 주입한다.
데이터만 바꿔 재생성하면 게임에 반영(코드 수정 없이).
## 향후 방향 (참고)
추후 카드·적 공격은 **메이플스토리 IP**에 맞춰 디벨롭 예정. 본 스키마는 명시적 `desc`
키 기반 확장으로 이를 수용한다(새 카드/적은 JSON 항목 추가로 확장). 본 작업은 현 3종+적1
기준 **최소 스키마**까지만 — 새 효과 필드(상태이상/드로우 등)는 추가하지 않는다(YAGNI).
## 단일 소스 원칙
생성물(`SlayDeckController.codeblock` · `ui/DefaultGroup.ui` · `common.gamelogic`)은
`gen-slaydeck.mjs`가 생성한다. D 이후 **데이터의 단일 소스는 `data/*.json`**, 생성 로직의
단일 소스는 `gen-slaydeck.mjs`. 결정적 출력 유지.
## 설계
### 신규 파일
**`data/cards.json`**
```json
{
"cards": {
"Strike": { "name": "타격", "cost": 1, "kind": "Attack", "damage": 6, "desc": "피해 6" },
"Defend": { "name": "방어", "cost": 1, "kind": "Skill", "block": 5, "desc": "방어도 5" },
"Bash": { "name": "강타", "cost": 2, "kind": "Attack", "damage": 10, "desc": "피해 10" }
},
"starterDeck": ["Strike","Strike","Strike","Strike","Strike","Defend","Defend","Defend","Defend","Bash"]
}
```
**`data/enemies.json`**
```json
{
"enemies": {
"slime": {
"name": "슬라임", "maxHp": 45,
"intents": [
{ "kind": "Attack", "value": 10 },
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 8 }
]
}
},
"activeEnemy": "slime"
}
```
- `desc`는 명시적(작성자 작성). `kind``"Attack"` 또는 `"Skill"`. 효과는 `damage`/`block`.
- `activeEnemy`로 현재 단일 전투의 적을 데이터에서 지정. 향후 E(맵 노드)에서 노드별 선택으로 확장.
### 생성기(`gen-slaydeck.mjs`) 변경
1. 상단에서 `readFileSync`+`JSON.parse``data/cards.json`·`data/enemies.json` 로드.
2. **검증(fail-fast)**: `starterDeck`의 모든 id가 `cards`에 존재해야 함; `activeEnemy`
`enemies`에 존재해야 함. 아니면 명확한 에러로 `process.exit(1)`(또는 throw).
3. `writeCodeblocks()``StartCombat`에서:
- `self.Cards = {...}``cards`에서 생성(Lua 테이블 직렬화 헬퍼).
- `self.DrawPile = {...}``starterDeck`에서 생성.
- `self.EnemyName`/`EnemyMaxHp`/`EnemyIntents`/`EnemyIntentIndex``enemies[activeEnemy]`에서 생성.
- codeblock 속성 `EnemyMaxHp` DefaultValue도 데이터 값으로.
4. `upsertUi()`의 DeckHud 카드 미리보기 배열·CombatHud 초기 텍스트(적 이름·`HP n/n`·첫 의도)를
동일 데이터에서 파생.
5. Lua 문자열 직렬화 시 한글/따옴표 이스케이프 주의(데이터 값은 따옴표·역슬래시 없는 단순 문자열 가정,
필요 시 escape 헬퍼).
### 데이터 흐름
`data/*.json``gen-slaydeck.mjs`(로드·검증·직렬화) → `SlayDeckController.codeblock`(Lua 테이블)
+ `ui/DefaultGroup.ui`(초기 텍스트) → 메이커 런타임.
## 검증
- `node tools/gen-slaydeck.mjs` 정상; JSON 유효; 2회 실행 결정적.
- `data/cards.json`에서 카드 1장 수치만 변경 → 재생성 → codeblock의 해당 카드 수치 변경
(생성기/codeblock 직접 수정 없이).
- 잘못된 데이터(starterDeck에 없는 id, 잘못된 activeEnemy) → 생성기가 명확히 실패.
- 메이커 Play: 기존 B 동작과 동일(데이터 동치이므로 회귀 없음).
## 범위 밖 (금지)
- 새 효과 필드(상태이상·드로우·복합효과) 추가. 새 카드 종류 대량 추가. F(시뮬레이터)·E(메타).