From 0a83dea2d857704dd3c80db950a9aa5e9e0b99ef Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 16 Jun 2026 08:12:58 +0900 Subject: [PATCH] =?UTF-8?q?docs(spec):=20Phase=202=20=EC=BA=90=EB=A6=AD?= =?UTF-8?q?=ED=84=B0=20=EC=84=A0=ED=83=9D=20=EB=A9=94=EC=9D=B4=EC=BB=A4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=91=20=ED=8C=8C=EC=9D=BC=EB=9F=BF=20=EC=84=A4?= =?UTF-8?q?=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit charselect를 생성중단→stock화(메이커 편집), 이미지는 컨트롤러 런타임 주입 (ClassPortraits/luaCharsTable), 경로 구동 유지. 패턴 b 검증 파일럿. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...026-06-16-charselect-maker-pilot-design.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-16-charselect-maker-pilot-design.md diff --git a/docs/superpowers/specs/2026-06-16-charselect-maker-pilot-design.md b/docs/superpowers/specs/2026-06-16-charselect-maker-pilot-design.md new file mode 100644 index 0000000..68cba8c --- /dev/null +++ b/docs/superpowers/specs/2026-06-16-charselect-maker-pilot-design.md @@ -0,0 +1,73 @@ +# Phase 2 — 캐릭터 선택 메이커 저작 파일럿 설계 + +작성일: 2026-06-16 +브랜치: `feature/charselect-maker-pilot` (Phase 1b `feature/cb-modularization`/PR #71 위에 스택) + +## 목표 + +하이브리드 UI 로드맵의 **패턴 (b)**(메이커 시각 편집) 검증 파일럿. **캐릭터 선택 화면**을 "생성기 소유 → 메이커 소유"로 이관한다: +- **레이아웃**(패널·카드 위치·버튼)은 메이커에서 시각 편집(생성기가 안 덮음). +- **동적 내용**(캐릭터 이미지·선택 테두리·상태 텍스트)은 `SlayDeckController`가 런타임에 **경로로 주입** = 컨트롤러 내용주입. + +성공 시 Phase 3에서 상점·전체덱 등으로 확장. + +## 현재 구조 (조사 결과) + +- charselect는 **생성 섹션**: `lib/ui-helpers.mjs`의 `GENERATED_UI_SECTIONS`(:17)·`UI_APPEND_ORDER`(:35)에 `'CharacterSelectHud'` 포함. `hud/charselect.mjs`의 `buildCharSelect()`가 엔티티 emit, `upsertUi`가 `emit('CharacterSelectHud', buildCharSelect())`. +- **이미지 = 생성 시 주입**: `hud/charselect.mjs:86` `sprite({ dataId: CHARS.portraits[cls.classId], … })`. 런타임 주입 아님. +- **컨트롤러는 경로 구동**: `cb/charselect.mjs`의 `RenderCharacterSelect`(각 `{Warrior,Mage,Thief}Button`의 `SpriteGUIRendererComponent.Color`로 선택 테두리 + Status 텍스트), `SelectClass`, `StartNewGame`. 바인딩은 `cb/state.mjs`의 `BindMenuButtons`(경로로 WarriorButton·BackButton·StartButton 등). 표시 토글은 `ShowState`(경로). **이미지 주입은 없음.** +- **런타임 시드 모델**: `self.CardFrames`를 `${luaFramesTable()}`로 OnBeginPlay(cb/boot)·StartRun(cb/run)에서 주입 → `ClassPortraits`의 모델. +- `upsertUi` 동작: 기존 `.ui` 로드 → 생성 섹션 엔티티 필터아웃 → emit 섹션 재추가. **생성 섹션에서 빠지면 `isGeneratedUiEntity=false`라 필터 안 됨 → 기존 엔티티 보존(stock)**. + +## 상세 설계 + +### ① 생성 중단 → stock화 (generate-once-then-stop) +- `lib/ui-helpers.mjs` `GENERATED_UI_SECTIONS`·`UI_APPEND_ORDER`에서 `'CharacterSelectHud'` 제거. +- `gen-slaydeck.mjs`(upsertUi)에서 `emit('CharacterSelectHud', buildCharSelect())` + 관련 import 제거. `hud/charselect.mjs`는 **삭제**(부트스트랩 완료 — git 이력에 레퍼런스 남음). +- 효과: 현재 `DefaultGroup.ui`의 charselect 엔티티가 그대로 **stock**으로 보존 → 메이커 시각 편집 가능, 재생성에 안 덮임. + +### ② 이미지 런타임 주입 (컨트롤러 내용주입 = 패턴 b 핵심) +- `lib/data.mjs`에 `luaCharsTable()` 신설(`data/characters.json`의 `portraits` 시드, `luaFramesTable`/`luaNodeIconsTable` 패턴; `self.ClassPortraits = { warrior="…", magician="…", bandit="…" }`). +- 주입 지점: `cb/boot.mjs` OnBeginPlay·`cb/run.mjs` StartRun에 `${luaCharsTable()}`(CardFrames 시드 옆) + prop `ClassPortraits`(any) 선언. +- `cb/charselect.mjs` `RenderCharacterSelect`에 이미지 주입 추가: 각 `{key}Button/Art` 엔티티의 `SpriteGUIRendererComponent.ImageRUID`를 `self.ClassPortraits[classId]`로 설정(경로별 isvalid 가드). → 메이커 레이아웃(빈/임의 Art)이어도 컨트롤러가 올바른 이미지 채움. **characters.json 데이터 구동 유지.** + +### ③ 경로 구동 유지 (무변경) +- 선택 테두리·Status·버튼 바인딩(`RenderCharacterSelect` 색/텍스트·`SelectClass`·`BindMenuButtons`·`StartNewGame`·`ShowState`)은 이미 경로 기반 → 변경 없음. + +### ④ 엔티티 경로 계약 (docs 명시) +메이커 편집 시 아래 경로 유지 필수(컨트롤러가 이 경로로 구동; 누락 시 isvalid 가드로 무시되되 그 부분 동작 안 함): +``` +/ui/DefaultGroup/CharacterSelectHud (루트, ShowState 토글) + /OpaqueBackdrop /Title /Status + /WarriorButton (+ /Art ← 이미지 주입, /NameBanner, /Name) + /ThiefButton (+ /Art, /NameBanner, /Name) + /MageButton (+ /Art, /NameBanner, /Name) + /StartButton /BackButton +``` +(#67로 DeckButton 제거됨.) classId 매핑: Warrior→warrior, Thief→bandit, Mage→magician. + +## 검증 (동작 — 바이트동일 아님) +- 생성기: charselect 제거 후 `node tools/deck/gen-slaydeck.mjs` → **charselect 외 산출물 무영향**(`diffcheck`로 codeblock·common 확인; ui는 charselect 섹션만 stock으로 잔류·다른 섹션 동일). charselect 엔티티가 ui에 존재(`count.mjs`). +- 메이커 플레이테스트: 로비→직업선택→**3 이미지가 컨트롤러 주입으로 표시**→클릭 시 금색테두리·Status→시작 시 그 직업으로 런→**메이커에서 카드 위치 이동 후 재생성해도 유지** 확인. + +## 범위 밖 +- 상점·전체덱 등 다른 화면(Phase 3). +- 새 UIGroup(.ui) 분리(경로·ShowState 재작업 큼) — DefaultGroup 내 stock으로 충분. +- 게임 규칙·다른 화면 변경. + +## 리스크 +- stock 전환 시 charselect 엔티티의 `.ui` 내 직렬화 위치 이동 가능 → 렌더는 경로/displayOrder 기반이라 무관하나 플레이테스트로 확인. +- 메이커가 경로를 바꾸면 계약 깨짐 → 경로 표로 가드. isvalid 가드로 크래시는 방지. +- 의존: Phase 1b(cb/charselect·boot·run) 위 스택. #70·#71 머지 후 main 리타겟. + +## 변경 파일 요약 +| 파일 | 변경 | +|---|---| +| `tools/deck/lib/ui-helpers.mjs` | `GENERATED_UI_SECTIONS`·`UI_APPEND_ORDER`에서 CharacterSelectHud 제거 | +| `tools/deck/gen-slaydeck.mjs` | upsertUi에서 charselect emit·import 제거 | +| `tools/deck/hud/charselect.mjs` | **삭제** | +| `tools/deck/lib/data.mjs` | `luaCharsTable()` 신설 | +| `tools/deck/cb/boot.mjs`·`cb/run.mjs` | `${luaCharsTable()}` 시드 + ClassPortraits prop | +| `tools/deck/cb/charselect.mjs` | `RenderCharacterSelect`에 Art ImageRUID 주입 | +| `docs/...charselect 경로 계약` | 경로 표(이 스펙 §④) | +| `ui/DefaultGroup.ui`·codeblock | 재생성(charselect는 stock 잔류) |