docs(spec): 생성기 모듈화(Phase 1) + 하이브리드 UI 로드맵 설계
gen-slaydeck.mjs UI emit 16종을 lib/+hud/ 모듈로 분리(출력 바이트 동일·무위험). codeblock 메서드 제외. 하이브리드 단계적: Phase2 캐릭터선택 메이커 저작 파일럿. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
# 생성기 모듈화 (Phase 1) + 하이브리드 UI 로드맵 설계
|
||||||
|
|
||||||
|
작성일: 2026-06-16
|
||||||
|
브랜치: `feature/gen-modularization`
|
||||||
|
|
||||||
|
## 배경 / 동기
|
||||||
|
|
||||||
|
`DefaultGroup.ui`에 모든 UI(캐릭터 선택·상점·전체 덱·전투…)가 들어 있어, 사용자가 (a) **생성기 코드 유지보수**와 (b) **메이커에서 기능별 시각 편집**을 원함.
|
||||||
|
|
||||||
|
핵심 제약(브레인스토밍에서 확정): MSW에서 "Layer"는 렌더 z순서일 뿐 논리 분리 도구가 아니고, 실제 UI 그룹 단위는 **UIGroup**(`.ui` 파일 = UIGroup, 현재 Default/Popup/Toast 3개). 그리고 **같은 UI를 '생성'과 '메이커 수동 편집' 둘 다로 둘 수 없음**(재생성이 수동 편집을 덮어씀).
|
||||||
|
|
||||||
|
→ **소유 모델 = 하이브리드·단계적**(사용자 승인): 정적 레이아웃은 메이커 저작(시각 편집), 동적 내용은 컨트롤러가 런타임 주입. 단, 한 번에 안 하고 단계적으로.
|
||||||
|
|
||||||
|
## 로드맵 (단계적 — 각 Phase가 자체 spec→plan)
|
||||||
|
|
||||||
|
- **Phase 1 (이 문서)**: `gen-slaydeck.mjs`(~6,200줄)의 **UI emit을 기능별 모듈로 분리**. 출력 `.ui`/`codeblock` **바이트 동일**(순수 리팩터·무위험). (a) 충족 + (b) 토대(화면별 파일).
|
||||||
|
- **Phase 2 (후속 spec)**: 화면 1개(**캐릭터 선택**) 파일럿 — 정적 레이아웃을 메이커 저작 UIGroup으로 이관, 생성기는 그 화면 emit 중단, `SlayDeckController`가 경로로 내용(이미지·텍스트) 주입. (b) 패턴 검증.
|
||||||
|
- **Phase 3 (후속 spec)**: 검증되면 상점·전체덱 등으로 확장.
|
||||||
|
|
||||||
|
## 현재 구조 (조사 결과)
|
||||||
|
|
||||||
|
- **공유 인프라**(~48–530): `luaSoulShopTable`/`luaFramesTable`/`luaNodeIconsTable`/`luaRelicsTable`/`luaPotionsTable`/`luaIntentsArray`/`luaEnemiesTable`/`luaStr`/`luaJobsTable`/`luaCardsTable`/`luaDeckTable`/`frameRuid`/`cardFaceLayout`/`guid`/`transform`/`sprite`/`button`/`text`/`scrollLayoutGroup`/`entity`/`uiPath`/`sectionRoot`/`isGeneratedUiEntity`/`appendUiSection`. 데이터 로드 상수(CARDS/CHARS/ENEMIES/RELICS/POTIONS/CARDFRAMES/NODEICONS/CAM) 및 색·치수 상수(GOLD/WHITE/TRANSPARENT/ALIGN_*/CARD_W/CARD_H 등).
|
||||||
|
- **`guid(prefix, n)`은 순수 함수**(`:284`, 내부 카운터 없음; ns는 prefix→바이트 매핑). **모듈 호출 순서와 무관하게 동일 guid** → 분리해도 바이트 동일.
|
||||||
|
- **`upsertUi()`**(`:529`)가 UI 오케스트레이터: 기존 `DefaultGroup.ui` 로드 → 생성 섹션 필터(stock 보존) → 로컬 `emit(section, entities)` 클로저로 누적 → CardHand 스톡카드 in-place upsert(`:565–691`, 특수) → HUD별 `const x=[]; const add=…; add(entity(...)); …; emit('X', x)` → (말미) 병합·기록.
|
||||||
|
- **HUD emit 16종(순서·라인)**: DeckHud(`:808`) · DeckInspectHud(`:942`) · DeckAllHud(`:1097`) · CombatHud(`:1587`) · RewardHud(`:1681`) · MapHud(`:1839`) · ShopHud(`:2038`) · RestHud(`:2095`) · TreasureHud(`:2181`) · JobChoiceHud(`:2229`) · JobSelectHud(`:2314`) · MainMenu(`:2616`) · CharacterSelectHud(`:2617`) · LobbyHud(`:2672`) · BoardHud(`:2727`) · SoulShopHud(`:2814`). **각 섹션은 서로의 지역변수 비참조**(헬퍼·데이터 상수만 사용).
|
||||||
|
- **codeblock 메서드**(`prop`/`method`/`codeblock`/`writeCodeblocks` `:2836–6124`, ~3,200줄) + **patchCommon**(`:6125`). **Phase 1 범위 제외.**
|
||||||
|
|
||||||
|
## Phase 1 상세 설계
|
||||||
|
|
||||||
|
### 목표 파일 구조
|
||||||
|
```
|
||||||
|
tools/deck/
|
||||||
|
gen-slaydeck.mjs # 오케스트레이터(축소): import lib+hud → 데이터 로드 → upsertUi(HUD 모듈 순차) → writeCodeblocks → patchCommon
|
||||||
|
lib/
|
||||||
|
ui-helpers.mjs # guid, transform, sprite, button, text, entity, scrollLayoutGroup,
|
||||||
|
# 상수(GOLD/WHITE/TRANSPARENT/ALIGN_*/CARD_W/CARD_H/UI_ROOT 등), cardFaceLayout,
|
||||||
|
# uiPath/sectionRoot/isGeneratedUiEntity/appendUiSection
|
||||||
|
data.mjs # CARDS/CHARS/ENEMIES/RELICS/POTIONS/CARDFRAMES/NODEICONS/CAM 로드·검증
|
||||||
|
# + luaXxxTable·frameRuid
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 모듈 계약
|
||||||
|
- 각 `hud/<name>.mjs`: `export function build<Name>()` → 자기 HUD 엔티티 배열 반환. 필요한 헬퍼·상수·데이터는 `lib/`에서 **import**(거대 deps 객체 전달 금지).
|
||||||
|
- `upsertUi()`(오케스트레이터에 잔류)는 기존 **순서 그대로** `emit('DeckHud', buildDeckHud())` … `emit('SoulShopHud', buildSoulShop())` 호출. `emit`·섹션 병합 로직 불변.
|
||||||
|
- **CardHand 스톡카드 in-place upsert**(`:565–691`)는 기존 `.ui` 엔티티를 변형하는 특수 로직 → 오케스트레이터(또는 `hud/cardhand.mjs`)에 그대로 유지. import 경계만 정리.
|
||||||
|
|
||||||
|
### 바이트 동일 불변식 (가장 중요)
|
||||||
|
- 리팩터는 **출력 변경 0**이 목표. 보장 근거: guid 순수·emit 순서·entity 구성 모두 보존, 로직 이동만.
|
||||||
|
- **합격 기준**: 리팩터 후 `node tools/deck/gen-slaydeck.mjs` → `git diff` 결과가 **`ui/DefaultGroup.ui`·`SlayDeckController.codeblock`에 0 변경**(`Global/common.gamelogic`은 LF churn만 허용 → `git checkout`).
|
||||||
|
|
||||||
|
### 증분 실행 전략
|
||||||
|
- 한 번에 16개 다 옮기지 말고 **HUD 1~2개씩 추출 → 재생성 → `git diff` 빈 결과 확인 → 커밋** 반복. 첫 추출(예: SoulShopHud 같은 말단 + lib 골격) 성공 후 패턴 반복.
|
||||||
|
- lib 추출(헬퍼·상수·데이터) 먼저 → 그 다음 HUD 모듈을 하나씩 lib import로 전환.
|
||||||
|
|
||||||
|
### 미러/테스트·하네스
|
||||||
|
- 전투규칙·맵생성 Lua **무변경** → `sim-balance`/`rogue-map` 미러 동기화 불필요(회귀 확인차 `node --test` 실행).
|
||||||
|
- **RULES 동기화**: 생성기가 다중 파일이 되므로 RULES §1 "단일 소스"/보조 생성기 표를 `tools/deck/`(gen-slaydeck + lib/ + hud/)로 갱신.
|
||||||
|
|
||||||
|
## 범위 밖 (명시)
|
||||||
|
- codeblock 메서드(`method()` ~3,200줄) 분리 — 더 크고 (b)와 무관·리스크↑. 원하면 별도 **Phase 1b** spec.
|
||||||
|
- 게임 동작·데이터·런타임 로직 변경. (순수 소스 리팩터)
|
||||||
|
- UIGroup 분할·메이커 저작 이관 — Phase 2 이후.
|
||||||
|
|
||||||
|
## 리스크
|
||||||
|
- 클로저 참조(헬퍼/상수)를 import로 전환하는 광범위·기계적 수정 — 누락 시 런타임 throw 또는 출력 diff로 **즉시** 노출(바이트 검증이 안전망).
|
||||||
|
- 상수 정의 위치 산재(CARD_W·GOLD·UI_ROOT 등 top-level) — lib로 이동 시 누락 주의. 추출 전 `grep`으로 전체 상수 인벤토리 작성.
|
||||||
|
- ESM 순환 import 주의(lib는 hud를 import하지 않음 — 단방향: orchestrator→hud→lib).
|
||||||
|
|
||||||
|
## 변경 파일 요약
|
||||||
|
| 파일 | 변경 |
|
||||||
|
|---|---|
|
||||||
|
| `tools/deck/lib/ui-helpers.mjs`, `lib/data.mjs` | **신설** — 공유 헬퍼·상수·데이터 |
|
||||||
|
| `tools/deck/hud/*.mjs` (16) | **신설** — HUD별 build 함수 |
|
||||||
|
| `tools/deck/gen-slaydeck.mjs` | 오케스트레이터로 축소(데이터/UI emit 본문 → 모듈로 이동, import·호출만) |
|
||||||
|
| `RULES.md` | §1 보조 생성기/단일소스 표에 lib/·hud/ 반영 |
|
||||||
|
| `ui/DefaultGroup.ui`·`SlayDeckController.codeblock` | **무변경**(바이트 동일이 합격 기준) |
|
||||||
Reference in New Issue
Block a user