# 카드/적 데이터 외부화 (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(메타).