charselect를 생성중단→stock화(메이커 편집), 이미지는 컨트롤러 런타임 주입 (ClassPortraits/luaCharsTable), 경로 구동 유지. 패턴 b 검증 파일럿. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.9 KiB
5.9 KiB
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:86sprite({ 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.mjsGENERATED_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.mjsOnBeginPlay·cb/run.mjsStartRun에${luaCharsTable()}(CardFrames 시드 옆) + propClassPortraits(any) 선언. cb/charselect.mjsRenderCharacterSelect에 이미지 주입 추가: 각{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 잔류) |