Files
maplecontest/docs/superpowers/plans/2026-06-16-generator-modularization.md

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) JOBS(:1940) SOUL_UNLOCKS(:4247) CARDFRAMES+검증(:5768) RARITIES(:58) NODEICONS+검증(:9296) CHARS+검증(:99103) CAM(: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) luaRelicsTable luaPotionsTable luaIntentsArray luaEnemiesTable luaStr luaJobsTable luaCardsTable luaDeckTable
    • 맨 위 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 };
    • ⚠️ luaNodeIconsTableluaStr를 쓰므로 luaStr도 같이 이동. luaFramesTableluaStr 사용. 상호 참조는 같은 모듈 내라 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) DAMAGE_DIGIT_RUIDS(:237) DAMAGE_POP_*(:249252) MAX_MONSTERS(:254) HEAD_OFFSET_Y(:255) HP_BAR_W(:257) WHITE(:258) CARD_NAME_TEXT CARD_DESC_TEXT(:259260) CARD_W CARD_H CARD_SPACING CARD_XS(:276279) ALIGN_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가 사용.
  • 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>); 형태다. 추출 절차:

  1. 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 호출 줄만 제외).
  2. upsertUi에서 해당 블록을 emit('<Name>', build<Name>()); 한 줄로 치환.
  3. 검증 게이트(ui/codeblock 0 변경) → churn 복원 → 커밋.
  4. ⚠️ 옮긴 블록이 upsertUi 지역변수(byPath/ui/cards/previewIds)를 참조하면 안 됨(HUD 섹션은 헬퍼·데이터만 씀이 확인됨). 참조 시 바이트 diff로 즉시 드러남 → 그 변수를 인자로 받도록 조정.

추출 대상(순서·소스 라인·emit명·모듈):

모듈 emit 소스(블록 시작~emit줄)
hud/deckhud.mjsbuildDeckHud DeckHud :693(const hud=[])~:808
hud/deckinspect.mjsbuildDeckInspect DeckInspectHud :810~:942
hud/deckall.mjsbuildDeckAll DeckAllHud :944~:1097
hud/combat.mjsbuildCombat CombatHud :1100~:1587
hud/reward.mjsbuildReward RewardHud :1589~:1681
hud/map.mjsbuildMap MapHud :1684~:1839
hud/shop.mjsbuildShop ShopHud :1841~:2038
hud/rest.mjsbuildRest RestHud :2040~:2095
hud/treasure.mjsbuildTreasure TreasureHud :2098~:2181
hud/jobchoice.mjsbuildJobChoice JobChoiceHud :2184~:2229
hud/jobselect.mjsbuildJobSelect JobSelectHud :2231~:2314
hud/mainmenu.mjsbuildMainMenu MainMenu :2316~:2616
hud/charselect.mjsbuildCharSelect CharacterSelectHud :2437~:2617(select[0]…enable=false 포함)
hud/lobby.mjsbuildLobby LobbyHud :2620~:2672
hud/board.mjsbuildBoard BoardHud :2675~:2727
hud/soulshop.mjsbuildSoulShop 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 처리 확인.

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)로 순환 없음.