11 KiB
생성기 모듈화 (Phase 1) 구현 계획
For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans 로 태스크 단위 구현. 단계는
- [ ]체크박스.
Goal: tools/deck/gen-slaydeck.mjs(~6,200줄)의 공유 인프라와 UI emit 16종을 lib/·hud/ 모듈로 분리하되 출력 산출물은 바이트 동일로 유지한다.
Architecture: 단방향 의존 — gen-slaydeck.mjs(오케스트레이터) → hud/*.mjs(HUD별 build 함수) → lib/*.mjs(헬퍼·상수·데이터). guid(prefix,n)가 순수 함수라 모듈화해도 emit 순서만 보존하면 출력 불변. codeblock 메서드는 이번 범위 제외.
Tech Stack: Node ESM. 검증 = 바이트 동일 재생성(git diff 빈 결과) + 미러 node --test.
🔑 검증 게이트 (모든 Task 공통)
각 추출 후 반드시:
node tools/deck/gen-slaydeck.mjs # 성공 메시지 1줄, throw 없음
git status --short
합격 기준: ui/DefaultGroup.ui·RootDesk/MyDesk/SlayDeckController.codeblock이 변경 안 됨(git status에 안 뜸). Global/common.gamelogic만 M이면 LF churn → git checkout -- Global/common.gamelogic.
- 만약 ui/codeblock이
M로 뜨면 추출 중 실수(참조 누락/순서 변경) →git diff --stat로 어느 산출물인지 보고 되돌려 원인 수정. (RULES상 산출물 content는 안 봄 — 소스 diff로 원인 파악.)
파일 구조 (목표)
tools/deck/
gen-slaydeck.mjs # 오케스트레이터: import → 데이터 로드(lib) → upsertUi(hud 호출) → writeCodeblocks → patchCommon
lib/
data.mjs # 데이터 로드·검증·luaXxxTable·frameRuid·게임상수
ui-helpers.mjs # guid/transform/sprite/button/text/entity/scrollLayoutGroup/cardFaceLayout/applySortingOverride
# + UI 상수 + uiPath/sectionRoot/isGeneratedUiEntity/appendUiSection
hud/
deckhud.mjs deckinspect.mjs deckall.mjs combat.mjs reward.mjs map.mjs
shop.mjs rest.mjs treasure.mjs jobchoice.mjs jobselect.mjs mainmenu.mjs
charselect.mjs lobby.mjs board.mjs soulshop.mjs
Task 1: lib/data.mjs — 데이터·게임상수·lua 테이블 추출
Files: Create tools/deck/lib/data.mjs; Modify tools/deck/gen-slaydeck.mjs(상단 데이터/lua 블록 → import)
-
Step 1:
lib/data.mjs생성. gen-slaydeck.mjs에서 아래를 잘라 이동(정의 본문 그대로):- 데이터 로드+검증:
CARDS(:3)ENEMIES(:4)CLASSES(:717)40)JOBS(:19SOUL_UNLOCKS(:4247)68)CARDFRAMES+검증(:57RARITIES(:58)NODEICONS+검증(:9296)103)CHARS+검증(:99CAM(:105)RELICS+검증(:107~)POTIONS+검증(:118~) - 게임 상수:
MAP_ROWS(:84)MAP_COLS(:85)CHEST_CLOSED_RUID(:88)CHEST_OPEN_RUID(:89) - 함수:
luaSoulShopTable(:48)frameRuid(:69)luaFramesTable(:72)luaNodeIconsTable(:78)luaRelicsTableluaPotionsTableluaIntentsArrayluaEnemiesTableluaStrluaJobsTableluaCardsTableluaDeckTable - 맨 위
import { readFileSync } from 'node:fs';, 맨 끝export { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, NODEICONS, CHARS, CAM, RELICS, POTIONS, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable }; - ⚠️
luaNodeIconsTable는luaStr를 쓰므로luaStr도 같이 이동.luaFramesTable도luaStr사용. 상호 참조는 같은 모듈 내라 OK.
- 데이터 로드+검증:
-
Step 2: gen-slaydeck.mjs 상단에
import { CARDS, ENEMIES, ... , luaDeckTable } from './lib/data.mjs';추가(이동한 정의 위치에). -
Step 3: 검증 게이트 실행 → ui/codeblock 0 변경 확인 → common.gamelogic churn 복원.
-
Step 4: 커밋
git add tools/deck/lib/data.mjs tools/deck/gen-slaydeck.mjs
git commit -m "refactor(gen): lib/data.mjs로 데이터·lua 테이블 추출 (출력 불변)"
Task 2: lib/ui-helpers.mjs — UI 헬퍼·상수 추출
Files: Create tools/deck/lib/ui-helpers.mjs; Modify tools/deck/gen-slaydeck.mjs
-
Step 1:
lib/ui-helpers.mjs생성. gen-slaydeck.mjs에서 이동:- UI 상수:
UI_FILE(:190)COMMON_FILE(:191)UI_ROOT(:192)GENERATED_UI_SECTIONS(:193)UI_APPEND_ORDER(:211)DISABLED_STOCK_CONTROLS(:229)TRANSPARENT DARK GOLD ATTACK DEFEND SKILL(:231236)252)DAMAGE_DIGIT_RUIDS(:237)DAMAGE_POP_*(:249MAX_MONSTERS(:254)HEAD_OFFSET_Y(:255)HP_BAR_W(:257)WHITE(:258)CARD_NAME_TEXT CARD_DESC_TEXT(:259260)279)CARD_W CARD_H CARD_SPACING CARD_XS(:276ALIGN_CENTER ALIGN_BOTTOM_CENTER(:281~282) - 헬퍼:
cardFaceLayout(:264)guid(:284)transform(:292)sprite(:317)button(:353)text(:378)scrollLayoutGroup(:405)popupLayerFor(:437)uiOrderFor(:443)displayOrderFor(:452)applySortingOverride(:456)entity(:472)uiPath(:504)sectionRoot(:508)isGeneratedUiEntity(:512)appendUiSection(:516) export { ... }전부.- ⚠️ COMMON_FILE은 patchCommon(:6125)도 사용 → export 필요. UI_APPEND_ORDER·GENERATED_UI_SECTIONS는 upsertUi가 사용.
- UI 상수:
-
Step 2: gen-slaydeck.mjs에
import { ... } from './lib/ui-helpers.mjs';추가. -
Step 3: 검증 게이트 → 0 변경 → churn 복원.
-
Step 4: 커밋
git commit -m "refactor(gen): lib/ui-helpers.mjs로 UI 헬퍼·상수 추출 (출력 불변)"
HUD 추출 공통 레시피 (Task 3~6에 반복 적용)
각 HUD는 현재 upsertUi() 안에서 const <v> = []; const add = (e) => <v>.push(e); add(entity(...)); …; emit('<Name>', <v>); 형태다. 추출 절차:
tools/deck/hud/<name>.mjs생성:import { guid, entity, transform, sprite, button, text, GOLD, ... } from '../lib/ui-helpers.mjs';+ 필요한 데이터는'../lib/data.mjs'.export function build<Name>() { const e = []; const add = (x)=>e.push(x); add(entity(...)); …; return e; }— 본문은 기존 라인 그대로 이동(emit 호출 줄만 제외).- upsertUi에서 해당 블록을
emit('<Name>', build<Name>());한 줄로 치환. - 검증 게이트(ui/codeblock 0 변경) → churn 복원 → 커밋.
- ⚠️ 옮긴 블록이 upsertUi 지역변수(
byPath/ui/cards/previewIds)를 참조하면 안 됨(HUD 섹션은 헬퍼·데이터만 씀이 확인됨). 참조 시 바이트 diff로 즉시 드러남 → 그 변수를 인자로 받도록 조정.
추출 대상(순서·소스 라인·emit명·모듈):
| 모듈 | emit | 소스(블록 시작~emit줄) |
|---|---|---|
hud/deckhud.mjs → buildDeckHud |
DeckHud | :693(const hud=[])~:808 |
hud/deckinspect.mjs → buildDeckInspect |
DeckInspectHud | :810~:942 |
hud/deckall.mjs → buildDeckAll |
DeckAllHud | :944~:1097 |
hud/combat.mjs → buildCombat |
CombatHud | :1100~:1587 |
hud/reward.mjs → buildReward |
RewardHud | :1589~:1681 |
hud/map.mjs → buildMap |
MapHud | :1684~:1839 |
hud/shop.mjs → buildShop |
ShopHud | :1841~:2038 |
hud/rest.mjs → buildRest |
RestHud | :2040~:2095 |
hud/treasure.mjs → buildTreasure |
TreasureHud | :2098~:2181 |
hud/jobchoice.mjs → buildJobChoice |
JobChoiceHud | :2184~:2229 |
hud/jobselect.mjs → buildJobSelect |
JobSelectHud | :2231~:2314 |
hud/mainmenu.mjs → buildMainMenu |
MainMenu | :2316~:2616 |
hud/charselect.mjs → buildCharSelect |
CharacterSelectHud | :2437~:2617(select[0]…enable=false 포함) |
hud/lobby.mjs → buildLobby |
LobbyHud | :2620~:2672 |
hud/board.mjs → buildBoard |
BoardHud | :2675~:2727 |
hud/soulshop.mjs → buildSoulShop |
SoulShopHud | :2729~:2814 |
⚠️ MainMenu/CharacterSelectHud는 const menu=[](:2316)·const select=[](:2437)로 인접 정의 후 emit('MainMenu', menu); emit('CharacterSelectHud', select);가 :2616~2617에 연속. 각각 별 모듈로 분리, emit 두 줄로. select[0].jsonString.enable=false(:2596)는 buildCharSelect 내부에서 e[0].jsonString.enable=false로.
⚠️ CardHand 스톡카드 in-place upsert(:557~691)는 추출 안 함 — 기존 .ui 엔티티를 변형하는 특수 로직이라 upsertUi에 잔류(import만 정리).
Task 3: HUD 추출 배치 A (말단부터 — 위험 낮은 순)
Files: Create hud/soulshop.mjs board.mjs lobby.mjs charselect.mjs; Modify gen-slaydeck.mjs
- Step 1~4: 레시피로 soulshop → board → lobby → charselect 순서로 하나씩 추출·검증·커밋(HUD 1개당 커밋 1개 권장, 배치로 묶어도 무방). 각 추출 후 검증 게이트 통과 필수.
- charselect는 P15/이번 작업으로 검증된 화면이라 패턴 안정.
e[0].jsonString.enable=false처리 확인.
- charselect는 P15/이번 작업으로 검증된 화면이라 패턴 안정.
Task 4: HUD 추출 배치 B
Files: Create hud/mainmenu.mjs jobselect.mjs jobchoice.mjs treasure.mjs rest.mjs; Modify gen-slaydeck.mjs
- Step 1~4: 레시피로 mainmenu → jobselect → jobchoice → treasure → rest 추출·검증·커밋.
Task 5: HUD 추출 배치 C
Files: Create hud/shop.mjs map.mjs reward.mjs; Modify gen-slaydeck.mjs
- Step 1~4: 레시피로 shop → map → reward 추출·검증·커밋.
Task 6: HUD 추출 배치 D (대형 — 마지막)
Files: Create hud/combat.mjs deckall.mjs deckinspect.mjs deckhud.mjs; Modify gen-slaydeck.mjs
- Step 1~4: 레시피로 deckhud → deckinspect → deckall → combat 추출·검증·커밋. combat(~487줄)이 가장 크니 마지막. 추출 후 upsertUi는 데이터 준비 + CardHand upsert +
emit('X', buildX())16줄 + 병합만 남아야 함.
Task 7: 마무리 — RULES 동기화 + 회귀 + PR
Files: Modify RULES.md
-
Step 1: RULES.md §1 보조 생성기/단일소스 표에 반영:
tools/deck/gen-slaydeck.mjs(오케스트레이터)·tools/deck/lib/*.mjs(공유)·tools/deck/hud/*.mjs(HUD별)가 함께ui/DefaultGroup.ui·SlayDeckController.codeblock의 단일 소스임을 명시. -
Step 2: 회귀 테스트(무영향 확인)
node --test tools/balance/sim-balance.test.mjs
node --test tools/map/rogue-map.test.mjs
Expected: 전부 pass(37/0, 9/0).
-
Step 3: 최종 재생성 + 전체 검증 게이트 → ui/codeblock 0 변경(누적) 최종 확인.
git status --short에 산출물 변경 없음. -
Step 4: RULES 커밋 → push → PR(
node tools/git/gitea-pr.mjs create <spec.json>, UTF-8).
Self-Review
- 스펙 커버리지: lib/data·ui-helpers(T1,T2) · HUD 16종 모듈화(T3~6) · 바이트 동일 게이트(공통 게이트, 매 Task) · codeblock 제외(범위 명시) · RULES 동기화(T7) · 미러 회귀(T7). 누락 없음.
- 플레이스홀더: 이동 대상은 라인 범위로 구체 지정, 검증 명령·합격기준 명시, export/import 목록 구체. "본문 그대로 이동"은 리팩터 특성상 코드 재타이핑 대신 정확한 소스 위치 지정(바이트 검증이 정확성 보장).
- 타입 일관성: build 함수명·emit명·모듈 경로 표로 고정. data.mjs/ui-helpers.mjs export ↔ gen-slaydeck import 일치.
- 리스크: 상수/헬퍼 참조 누락 → 바이트 diff 또는 throw로 즉시 노출. 증분 추출로 실패 범위 최소화. 단방향 의존(orchestrator→hud→lib)로 순환 없음.