# 노드 맵 UI 강화 구현 계획 > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. 설계: `docs/superpowers/specs/2026-06-15-node-map-ui-design.md`. 산출물(`ui/DefaultGroup.ui`·`*.codeblock`)은 Read/Edit 금지 — `tools/deck/gen-slaydeck.mjs` 소스·`data/*.json`만 수정 후 재생성. 검증은 `node tools/verify/count.mjs`(카운트)와 메이커 플레이테스트. **Goal:** 맵 노드 선택 화면(MapHud)을 단색 박스+텍스트 → 공식 메이플 아이콘 노드 + 배경 이미지로 강화하고, 아이콘/배경 RUID를 `data/nodeicons.json`로 외부화해 교체를 쉽게 한다. **Architecture:** 단일 소스(`data/nodeicons.json` + `tools/deck/gen-slaydeck.mjs`) → 산출물 재생성. 노드 = 아이콘 스프라이트(타입별 ImageRUID 런타임 주입, 상태는 Color 틴트), 배경 = MapHud 루트 이미지 + 반투명 오버레이. 절차 랜덤 배치·간선·버튼 바인딩 불변. **Tech Stack:** Node.js ESM 생성기, MSW Lua(codeblock). **확정 RUID** (공식 maplestory, 썸네일 검수): combat=`f98db6823e894a4f90308d61f75894ac`, elite=`793ed8a757534b89a82f460747d2df24`, boss=`423056cdbbc04f4da131b9721c404d96`, shop=`da37e1fac55d455b9ade08569f09f798`, rest=`b86c1b0568bd45f3ae4a4b97e1b4a594`, treasure=`f8a6d58e20f54e2ca899485055df1ce4`, background=`d84241f17de344a097f5b96ac914f1d2`. **현재 코드 기준선**(gen-slaydeck.mjs): MapHud emit `1662~1763`(루트 `1664`, pushMapNode `1696`, 그리드 `1727`, 도트 displayOrder 1), RenderMapNode `5615~5677`, luaFramesTable `72`, OnBeginPlay 주입 `2906`, StartRun 주입 `3361`, CardFrames prop `2854`, CHEST 상수 `84`, sprite 헬퍼 `297`(dataId→ImageRUID, type 0=이미지). --- ### Task 1: `data/nodeicons.json` + 생성기 로드·검증·직렬화 **Files:** Create `data/nodeicons.json` · Modify `tools/deck/gen-slaydeck.mjs` - [ ] **Step 1:** `data/nodeicons.json` 생성: ```json { "icons": { "combat": "f98db6823e894a4f90308d61f75894ac", "elite": "793ed8a757534b89a82f460747d2df24", "boss": "423056cdbbc04f4da131b9721c404d96", "shop": "da37e1fac55d455b9ade08569f09f798", "rest": "b86c1b0568bd45f3ae4a4b97e1b4a594", "treasure": "f8a6d58e20f54e2ca899485055df1ce4" }, "background": "d84241f17de344a097f5b96ac914f1d2" } ``` - [ ] **Step 2:** `gen-slaydeck.mjs` CHEST 상수(`85`) 아래에 로드+검증 추가: ```js // 노드 맵 아이콘/배경 (공식 maplestory RUID, data/nodeicons.json 단일 소스 — 교체 시 이 파일만 수정 후 재생성) const NODEICONS = JSON.parse(readFileSync('data/nodeicons.json', 'utf8')); for (const t of ['combat', 'elite', 'boss', 'shop', 'rest', 'treasure']) { if (!/^[0-9a-f]{32}$/.test((NODEICONS.icons || {})[t] || '')) throw new Error(`[gen-slaydeck] nodeicons.json icons.${t} RUID 누락/형식오류`); } if (!/^[0-9a-f]{32}$/.test(NODEICONS.background || '')) throw new Error('[gen-slaydeck] nodeicons.json background RUID 누락/형식오류'); ``` - [ ] **Step 3:** `luaFramesTable`(`77`) 직후에 직렬화 헬퍼 추가: ```js function luaNodeIconsTable() { const rows = Object.entries(NODEICONS.icons).map(([t, ruid]) => `\t${t} = ${luaStr(ruid)},`).join('\n'); return `self.NodeIcons = {\n${rows}\n}`; } ``` - [ ] **Step 4:** prop 선언 추가 — `prop('any', 'CardFrames'),`(`2854`) 아래에 `prop('any', 'NodeIcons'),`. - [ ] **Step 5:** OnBeginPlay 주입 — `2906`의 `${luaFramesTable()}` 줄 **아래**에 `${luaNodeIconsTable()}` 추가. StartRun 주입(`3361`)의 `${luaFramesTable()}` 아래에도 동일 추가. - [ ] **Step 6:** 로드 검증(아직 산출물 미변경이라 생성만 확인): ```bash node -e "const n=require('./data/nodeicons.json'); console.log('icons',Object.keys(n.icons).join(','),'| bg',n.background.length)" ``` 기대: `icons combat,elite,boss,shop,rest,treasure | bg 32` - [ ] **Step 7:** 커밋: ```bash git add data/nodeicons.json tools/deck/gen-slaydeck.mjs git commit -m "feat(node-map): nodeicons.json 외부화 + 생성기 로드·검증·NodeIcons 직렬화" ``` --- ### Task 2: MapHud emit — 배경 이미지 + 오버레이 + 아이콘 노드 **Files:** Modify `tools/deck/gen-slaydeck.mjs` - [ ] **Step 1:** MapHud 루트 sprite(`1673`)를 **배경 이미지**로 변경: ```js sprite({ dataId: NODEICONS.background, color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), ``` - [ ] **Step 2:** 루트 push(`1677` `map.push(mapHud);`) 직후, Title push 앞에 **반투명 오버레이 자식** 추가: ```js map.push(entity({ id: guid('map', 990), path: '/ui/DefaultGroup/MapHud/Overlay', modelId: 'uisprite', entryId: 'UISprite', componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', displayOrder: 0, components: [ transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), sprite({ color: { r: 0.04, g: 0.05, b: 0.09, a: 0.5 }, type: 1, raycast: true }), ], })); ``` (guid 'map',990 은 노드 그리드·도트가 쓰는 mapN(2~약189)보다 충분히 높아 충돌 없음. 빌드 끝 id 유일성 검증이 잡아줌.) - [ ] **Step 3:** Title displayOrder를 오버레이(0) 위로 — Title 엔티티(`1684` `displayOrder: 0,`)를 `displayOrder: 2,`로 변경. - [ ] **Step 4:** `pushMapNode`(`1696~1726`) — 노드 본체를 **아이콘**으로 + Label 자식 제거: - 본체 sprite(`1707`)를 `sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }),`로 변경(단색 박스 → 이미지, 런타임에 ImageRUID 주입). - Label 자식 push 블록(`1713~1725`, `map.push(entity({ ... /Label ... }))` 전체)을 **삭제**. - [ ] **Step 5:** 노드 크기 키움 — 그리드 호출(`1729`)의 `{ x: 56, y: 56 }`을 `{ x: 64, y: 64 }`로, 보스 호출(`1732`)의 `{ x: 72, y: 72 }`을 `{ x: 88, y: 88 }`로 변경. - [ ] **Step 6:** 커밋(아직 RenderMapNode 미수정 — 다음 Task와 함께 재생성/검증): ```bash git add tools/deck/gen-slaydeck.mjs git commit -m "feat(node-map): MapHud 배경 이미지+오버레이, 노드 아이콘화(라벨 제거·확대)" ``` --- ### Task 3: RenderMapNode Lua — ImageRUID + 상태 틴트 **Files:** Modify `tools/deck/gen-slaydeck.mjs` - [ ] **Step 1:** `RenderMapNode` 메서드 본문(`5615~5677`)을 아래로 **교체**(타입별 박스색/라벨 → 아이콘 ImageRUID + 상태 틴트). Lua 들여쓰기는 기존과 동일하게 실제 탭: ```lua local base = "/ui/DefaultGroup/MapHud/Node_" .. id local e = _EntityService:GetEntityByPath(base) if e == nil then return end local node = self.MapNodes[id] if node == nil then e.Enable = false return end e.Enable = true local ruid = self.NodeIcons[node.type] if ruid == nil then ruid = self.NodeIcons["combat"] end if e.SpriteGUIRendererComponent ~= nil and ruid ~= nil then e.SpriteGUIRendererComponent.ImageRUID = ruid end local reachable = self:IsReachable(id) local visited = false if self.VisitedNodes ~= nil then for i = 1, #self.VisitedNodes do if self.VisitedNodes[i] == id then visited = true end end end if e.SpriteGUIRendererComponent ~= nil then if id == self.CurrentNodeId then e.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1) elseif visited == true then e.SpriteGUIRendererComponent.Color = Color(0.5, 0.5, 0.55, 0.9) elseif reachable == true then e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1) else e.SpriteGUIRendererComponent.Color = Color(0.4, 0.4, 0.45, 0.45) end end if e.ButtonComponent ~= nil then e.ButtonComponent.Enable = reachable end ``` (메서드 시그니처 `[{Type:'string',...,Name:'id'}]`는 유지. `self:SetText(base.."/Label", ...)` 호출은 라벨 제거로 사라짐 — RenderMapDots/RenderMap는 불변.) - [ ] **Step 2:** 재생성: ```bash node tools/deck/gen-slaydeck.mjs ``` 기대: "Slay deck UI and combat codeblocks generated." - [ ] **Step 3:** 카운트 검증(내용 출력 금지, node fs): ```bash node -e "const fs=require('fs');const cb=fs.readFileSync('RootDesk/MyDesk/SlayDeckController.codeblock','utf8');const ui=fs.readFileSync('ui/DefaultGroup.ui','utf8');const c=(s,p)=>(s.match(new RegExp(p,'g'))||[]).length;console.log('NodeIcons inject:',c(cb,'self.NodeIcons ='),'(>=2: OnBeginPlay+StartRun)','| ImageRUID in RenderMapNode:',c(cb,'NodeIcons\\\\[node.type\\\\]'),'| UI MapHud/Overlay:',c(ui,'MapHud/Overlay'),'(1)','| UI Label nodes(0 기대):',c(ui,'Node_r1c1/Label'),'| bg RUID:',c(ui,'d84241f17de344a097f5b96ac914f1d2'));" ``` 기대: NodeIcons inject ≥2, ImageRUID ≥1, Overlay 1, Label 0, bg RUID ≥1. - [ ] **Step 4:** 커밋: ```bash git add tools/deck/gen-slaydeck.mjs ui/DefaultGroup.ui RootDesk/MyDesk/SlayDeckController.codeblock git commit -m "feat(node-map): RenderMapNode 아이콘 ImageRUID+상태 틴트, 재생성" ``` --- ### Task 4: 미러/회귀 테스트 - [ ] **Step 1:** 전투/맵그래프 미러 미변경 확인 — 테스트 실행: ```bash node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs ``` 기대: 전부 PASS(이 변경은 UI만, 전투/맵그래프 무관). - [ ] **Step 2:** `git status --short`로 의도치 않은 산출물 변경 없는지 확인. --- ### Task 5: 메이커 플레이테스트 - [ ] **Step 1:** `maker_refresh_workspace` → `maker_logs build`로 빌드 에러 0 확인(기존 BuySoulUnlock Info 경고는 무관). - [ ] **Step 2:** `maker_play` → 런 시작(`SelectClass`+`StartNewGame`) → 맵 화면 `maker_screenshot`. 검증: - 배경 이미지(리스항구) + 어두운 오버레이 위에 노드들. - 노드가 **타입별 아이콘**(주황버섯/골렘/발록/돈주머니/모닥불/상자)으로 표시, 라벨 텍스트 없음. - 상태 틴트: 현재=금색, 도달가능=원색(밝게), 잠김=어둡고 흐릿. - 도달 가능 노드 클릭 시 진행(`PickNode`/마우스). 랜덤 배치 정상. - 아이콘 잘림/왜곡 점검(특히 보스 발록·골렘). 잘리면 해당 노드 size 또는 아이콘 RUID 조정. - [ ] **Step 2b:** 실패 시 디버깅 — 흰박스→RUID/리로드 확인, 아이콘 안 뜸→ImageRUID 주입·NodeIcons 시드 확인, 가독성→오버레이 알파/틴트 튜닝. 생성기 수정→재생성→refresh→재플레이. - [ ] **Step 3:** `maker_stop`. 스크린샷 사용자 공유. --- ### Task 6: PR - [ ] **Step 1:** `git push -u origin feature/node-map-ui`(인증 실패 시 `GCM_INTERACTIVE=never GIT_TERMINAL_PROMPT=0 git push`로 재시도). - [ ] **Step 2:** UTF-8 spec JSON 작성 후 `node tools/git/gitea-pr.mjs create `. 제목 "feat: 노드 맵 UI 강화 — 아이콘 노드 + 배경 이미지(nodeicons.json 외부화)". - [ ] **Step 3:** 사용자에게 PR 번호 보고. (변경 용이성: `data/nodeicons.json` RUID만 바꾸고 `node tools/deck/gen-slaydeck.mjs` 재실행하면 교체됨을 명시.)