chore(verify): UIGroup 매핑·재연결 GAP 검증 헬퍼 추가
- uimap.mjs: .ui별 섹션/엔티티 카운트 매핑 (deny 우회, 카운트만) - cbgap.mjs: cb 참조 경로↔새 UIGroup 대조, GAP 분류 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
tools/verify/cbgap.mjs
Normal file
69
tools/verify/cbgap.mjs
Normal file
@@ -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}) ===`);
|
||||
Reference in New Issue
Block a user