Files
maplecontest/docs/superpowers/specs/2026-06-16-charselect-maker-pilot-design.md
gahusb 0a83dea2d8 docs(spec): Phase 2 캐릭터 선택 메이커 저작 파일럿 설계
charselect를 생성중단→stock화(메이커 편집), 이미지는 컨트롤러 런타임 주입
(ClassPortraits/luaCharsTable), 경로 구동 유지. 패턴 b 검증 파일럿.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:12:58 +09:00

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.mjsGENERATED_UI_SECTIONS(:17)·UI_APPEND_ORDER(:35)에 'CharacterSelectHud' 포함. hud/charselect.mjsbuildCharSelect()가 엔티티 emit, upsertUiemit('CharacterSelectHud', buildCharSelect()).
  • 이미지 = 생성 시 주입: hud/charselect.mjs:86 sprite({ dataId: CHARS.portraits[cls.classId], … }). 런타임 주입 아님.
  • 컨트롤러는 경로 구동: cb/charselect.mjsRenderCharacterSelect(각 {Warrior,Mage,Thief}ButtonSpriteGUIRendererComponent.Color로 선택 테두리 + Status 텍스트), SelectClass, StartNewGame. 바인딩은 cb/state.mjsBindMenuButtons(경로로 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.mjsluaCharsTable() 신설(data/characters.jsonportraits 시드, 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.ImageRUIDself.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.mjscharselect 외 산출물 무영향(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 잔류)