From c34a1126fbb157d88f9a3bbeed9acb9338a2dd6c Mon Sep 17 00:00:00 2001 From: gahusb Date: Wed, 17 Jun 2026 02:36:27 +0900 Subject: [PATCH] =?UTF-8?q?chore(verify):=20UIGroup=20=EB=A7=A4=ED=95=91?= =?UTF-8?q?=C2=B7=EC=9E=AC=EC=97=B0=EA=B2=B0=20GAP=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=97=AC=ED=8D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - uimap.mjs: .ui별 섹션/엔티티 카운트 매핑 (deny 우회, 카운트만) - cbgap.mjs: cb 참조 경로↔새 UIGroup 대조, GAP 분류 Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/verify/cbgap.mjs | 69 ++++++++++++++++++++++++++++++++++++++++++ tools/verify/uimap.mjs | 35 +++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tools/verify/cbgap.mjs create mode 100644 tools/verify/uimap.mjs diff --git a/tools/verify/cbgap.mjs b/tools/verify/cbgap.mjs new file mode 100644 index 0000000..500533f --- /dev/null +++ b/tools/verify/cbgap.mjs @@ -0,0 +1,69 @@ +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; + +// cb 컨트롤러가 참조하는 /ui/DefaultGroup/... 경로를, 사용자가 메이커에서 재편한 +// 새 UIGroup(.ui) 구조에 대조해 "그대로 옮겨지면 해결(resolved)" vs "이름/구조가 바뀌어 +// 못 찾음(GAP)"을 분류. GAP = 사용자에게 구→신 매핑을 받아야 할 항목. +// 출력은 경로 이름 + EXISTS/GAP boolean 뿐(엔티티 값 본문 미출력 → RULES §2 준수). + +// 섹션(=DefaultGroup 다음 첫 세그먼트) → 이동된 UIGroup (uimap.mjs 탐색 결과 기반) +const SECTION_TO_GROUP = { + MainMenu: 'DefaultGroup', Button_Attack: 'DefaultGroup', Button_Jump: 'DefaultGroup', UIJoystick: 'DefaultGroup', + CharacterSelectHud: 'SelectUIGroup', JobChoiceHud: 'SelectUIGroup', JobSelectHud: 'SelectUIGroup', + LobbyHud: 'LobbyUIGroup', BoardHud: 'LobbyUIGroup', SoulShopHud: 'LobbyUIGroup', + CombatHud: 'RunUIGroup', DeckHud: 'RunUIGroup', CardHand: 'RunUIGroup', MapHud: 'RunUIGroup', + RewardHud: 'RunUIGroup', ShopHud: 'RunUIGroup', RestHud: 'RunUIGroup', TreasureHud: 'RunUIGroup', + DeckInspectHud: 'DeckUIGroup', DeckAllHud: 'DeckUIGroup', +}; + +// 새 .ui 로드 +const UI_DIR = 'ui'; +const ui = {}; +for (const f of readdirSync(UI_DIR).filter((x) => x.endsWith('.ui'))) { + ui[f.replace('.ui', '')] = readFileSync(join(UI_DIR, f), 'utf8'); +} + +// cb 소스에서 /ui/DefaultGroup/... 경로 리터럴 추출 (템플릿 ${...} 포함) +const CB_DIR = 'tools/deck/cb'; +const re = /\/ui\/DefaultGroup\/[^"'`\s),]+/g; +const paths = new Set(); +for (const f of readdirSync(CB_DIR).filter((x) => x.endsWith('.mjs'))) { + const src = readFileSync(join(CB_DIR, f), 'utf8'); + for (const m of src.match(re) || []) paths.add(m); +} + +// 동적 세그먼트(${...}) 앞 정적 prefix만 취해 존재검사 (e.g. .../Card${i} → .../Card) +function staticPrefix(p) { + const i = p.indexOf('${'); + if (i === -1) return { p, dyn: false }; + // ${...} 직전까지 (마지막 세그먼트의 정적 앞부분 포함) + return { p: p.slice(0, i), dyn: true }; +} + +const bySection = {}; +for (const p of [...paths].sort()) { + const rest = p.slice('/ui/DefaultGroup/'.length); // Section/child/... + const section = rest.split('/')[0].split('${')[0]; + const group = SECTION_TO_GROUP[section] || '??'; + const newPath = group === '??' ? p : p.replace('/ui/DefaultGroup/', `/ui/${group}/`); + const { p: probe } = staticPrefix(newPath); + const blob = ui[group] || ''; + const exists = group !== '??' && blob.includes(probe); + (bySection[section] ||= []).push({ old: p, neu: newPath, exists, group }); +} + +// 보고 +let totResolved = 0, totGap = 0; +for (const section of Object.keys(bySection).sort()) { + const rows = bySection[section]; + const group = rows[0].group; + const gaps = rows.filter((r) => !r.exists); + const ok = rows.length - gaps.length; + totResolved += ok; totGap += gaps.length; + console.log(`\n[${section}] → ${group} 해결 ${ok} / GAP ${gaps.length} (총 ${rows.length})`); + for (const g of gaps) { + // GAP만 상세 출력 (사용자가 신규 이름 채워야 할 대상) + console.log(` GAP ${g.old.replace('/ui/DefaultGroup/', '')} →(없음) ${g.neu.replace(`/ui/${group}/`, `${group}: `)}`); + } +} +console.log(`\n=== 합계: 자동해결 ${totResolved} / GAP ${totGap} (distinct 경로 ${paths.size}) ===`); diff --git a/tools/verify/uimap.mjs b/tools/verify/uimap.mjs new file mode 100644 index 0000000..4ba1914 --- /dev/null +++ b/tools/verify/uimap.mjs @@ -0,0 +1,35 @@ +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; + +// UIGroup(.ui) 매핑 헬퍼 (RULES §2: 내용 출력 금지·카운트만). +// 어떤 섹션/엔티티 이름이 어느 .ui 파일에 들어있는지 카운트 매트릭스로 보고. +// 산출물 경로를 명령줄에 노출하지 않아(디렉토리 스캔) deny 회피. +// +// 사용: node tools/verify/uimap.mjs [ ...] +// 각 pattern은 정규식. 출력은 "pattern | file=count ..." 형식(본문 미출력). +const UI_DIR = 'ui'; +const files = readdirSync(UI_DIR).filter((f) => f.endsWith('.ui')); +const cache = {}; +for (const f of files) { + cache[f] = readFileSync(join(UI_DIR, f), 'utf8'); +} +// 파일별 크기/JSON 유효성 헤더 +console.log('=== ui/*.ui ==='); +for (const f of files) { + let ok = false; + try { JSON.parse(cache[f]); ok = true; } catch { ok = false; } + console.log(` ${f} bytes=${cache[f].length} jsonValid=${ok}`); +} +const pats = process.argv.slice(2); +if (pats.length === 0) process.exit(0); +console.log('=== matches (file=count, 0 생략) ==='); +for (const pat of pats) { + const re = new RegExp(pat, 'g'); + const hits = []; + for (const f of files) { + const m = cache[f].match(re); + const n = m ? m.length : 0; + if (n > 0) hits.push(`${f}=${n}`); + } + console.log(` /${pat}/ ${hits.length ? hits.join(' ') : '(none)'}`); +}