7.2 KiB
Phase 2 — 캐릭터 선택 메이커 저작 파일럿 구현 계획
For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans 로 태스크 단위 구현.
Goal: charselect를 생성중단→stock화(메이커 편집)하고, 캐릭터 이미지를 컨트롤러가 런타임 경로 주입(ClassPortraits)하도록 바꿔 패턴 (b)를 검증한다.
Architecture: ① 이미지 런타임 주입 추가(ClassPortraits + luaCharsTable + RenderCharacterSelect) → ② charselect 생성 중단(GENERATED_UI_SECTIONS/emit 제거 → 기존 엔티티 stock 보존). 컨트롤러는 경로 구동 유지.
Tech Stack: Node ESM 생성기, MSW Lua. 검증 = count(동작 검증) + 메이커 플레이테스트(바이트동일 아님 — codeblock·ui 의도적 변경).
의존: Phase 1b(#71) 위 스택(feature/charselect-maker-pilot). #70·#71 머지 후 main 리타겟.
검증 메모
Phase 2는 codeblock·ui를 의도적으로 변경(diffcheck-IDENTICAL 아님). 게이트:
node tools/deck/gen-slaydeck.mjs성공(throw 없음).node tools/verify/count.mjs cb ClassPortraits 'ImageRUID = self.ClassPortraits'→ 주입 코드 존재.node tools/verify/count.mjs ui CharacterSelectHud/WarriorButton/Art→ charselect 엔티티 ui 잔류(stock).- 미러 테스트 무영향(회귀 확인차 실행).
- 최종: 사용자 메이커 플레이테스트.
Task 1: luaCharsTable() 신설 (lib/data.mjs)
Files: Modify tools/deck/lib/data.mjs
- Step 1:
luaNodeIconsTable(:78-81) 바로 뒤에 추가:
function luaCharsTable() {
const rows = Object.entries(CHARS.portraits).map(([c, ruid]) => `\t${c} = ${luaStr(ruid)},`).join('\n');
return `self.ClassPortraits = {\n${rows}\n}`;
}
- Step 2:
export { ... }에luaCharsTable추가. - Step 3: 커밋(아직 미사용 — import 시 검증).
Task 2: ClassPortraits 시드 + prop
Files: Modify tools/deck/cb/boot.mjs, tools/deck/cb/run.mjs, tools/deck/gen-slaydeck.mjs
- Step 1:
cb/boot.mjs·cb/run.mjs의 import에luaCharsTable추가(luaNodeIconsTable옆,from '../lib/data.mjs'). - Step 2:
cb/boot.mjs:8(${luaNodeIconsTable()}) 다음 줄에${luaCharsTable()}추가.cb/run.mjs:34동일. - Step 3:
gen-slaydeck.mjs:311(prop('any', 'NodeIcons'),) 다음 줄에prop('any', 'ClassPortraits'),추가. - Step 4:
node tools/deck/gen-slaydeck.mjs성공 +node tools/verify/count.mjs cb ClassPortraits→ ≥2(시드 2회). - Step 5: 산출물 churn 복원(
git checkout --) — codeblock은 이 시점 변경됨(ClassPortraits 추가)이므로 복원 안 함, ui/common만 churn이면 복원. 커밋(소스 + 재생성 codeblock 분리 또는 함께 "산출물 재생성" 명시).
Task 3: RenderCharacterSelect 이미지 런타임 주입
Files: Modify tools/deck/cb/charselect.mjs:13(RenderCharacterSelect)
- Step 1: RenderCharacterSelect 본문 맨 앞에 3 Art 주입 추가(Python 치환 — 실탭). classId: Warrior→warrior, Mage→magician, Thief→bandit:
local warriorArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton/Art")
if warriorArt ~= nil and warriorArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["warrior"] ~= nil then
warriorArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["warrior"]
end
local mageArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton/Art")
if mageArt ~= nil and mageArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["magician"] ~= nil then
mageArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["magician"]
end
local thiefArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton/Art")
if thiefArt ~= nil and thiefArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["bandit"] ~= nil then
thiefArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["bandit"]
end
(기존 border/status 로직 앞에 prepend. RenderCharacterSelect는 ShowCharacterSelect/SelectClass에서 호출 → 열림·선택 시 멱등 주입.)
- Step 2:
node tools/deck/gen-slaydeck.mjs+node tools/verify/count.mjs cb 'ImageRUID = self.ClassPortraits'→ 3. - Step 3: 커밋(소스 + 재생성 codeblock).
Task 4: charselect 생성 중단 → stock
Files: Modify tools/deck/lib/ui-helpers.mjs, tools/deck/gen-slaydeck.mjs; Delete tools/deck/hud/charselect.mjs
- Step 1:
lib/ui-helpers.mjs의GENERATED_UI_SECTIONS·UI_APPEND_ORDER두 배열에서'CharacterSelectHud',줄 제거(2곳). - Step 2:
gen-slaydeck.mjs에서import { buildCharSelect } from './hud/charselect.mjs';(:38)와emit('CharacterSelectHud', buildCharSelect());(:229) 제거. - Step 3:
git rm tools/deck/hud/charselect.mjs(부트스트랩 완료, git 이력에 레퍼런스 잔존). - Step 4:
node tools/deck/gen-slaydeck.mjs성공 +node tools/verify/count.mjs ui CharacterSelectHud/WarriorButton/Art→ >0(charselect 엔티티가 stock으로 ui에 잔류).git status로 ui 변경 확인(charselect가 생성→stock 전환, 위치 이동 가능 — 정상). - Step 5: 커밋(소스 + 재생성 산출물, 메시지에 "charselect 생성 중단·stock화" 명시).
Task 5: 마무리 — RULES·경로계약·회귀·PR
Files: Modify RULES.md
- Step 1: RULES §1에 한 줄: charselect는 **메이커 저작(stock)**이라 생성 안 함 — 컨트롤러가
ClassPortraits로 이미지 런타임 주입, 메이커 편집 시 §스펙 경로 유지. (다른 화면은 여전히 hud/cb 생성.) - Step 2: 회귀:
node --test tools/balance/sim-balance.test.mjs·node --test tools/map/rogue-map.test.mjs(exit 0). - Step 3: push → PR(
node tools/git/gitea-pr.mjs create <spec.json>, base=feature/cb-modularization, 한국어). - Step 4: 사용자 메이커 플레이테스트(워크스페이스 reload 후): 로비→직업선택→3 이미지 컨트롤러 주입 표시→클릭 금색테두리·Status→시작 그 직업→메이커에서 카드 위치 이동·저장 후
node gen-slaydeck재생성해도 charselect 유지(stock 비파괴) 확인. 이미지 비표시 시 ClassPortraits 시드/주입 경로 점검.
Self-Review
- 스펙 커버리지: ①stock화(T4) ②런타임주입(T1-3: luaCharsTable·시드·prop·RenderCharacterSelect) ③경로구동 유지(무변경) ④경로계약(T5·스펙). 누락 없음.
- 플레이스홀더: luaCharsTable·주입 Lua·제거 라인 구체. 검증=count+playtest(바이트동일 아님 명시).
- 타입 일관성:
self.ClassPortraits(prop)↔luaCharsTable(self.ClassPortraits=)↔RenderCharacterSelect 참조 일치. classId Warrior→warrior/Mage→magician/Thief→bandit 일관. - 순서: 추가(주입 T1-3) 먼저 → 중단(stock T4). 중단 전엔 생성+주입 공존(무해), 중단 후 stock+주입.
- 리스크: 메이커 경로 변경 시 계약 깨짐(isvalid 가드로 크래시 방지·해당부 미동작). stock 전환 시 ui 위치 이동(렌더 무관).