feat(M02): 구조물 옵션 정의 = 명세 이름 고치기 칸(명세 있는 종류 · 52-11)

- 명세가 있는 종류(STRUCTURE_SPECS)는 컨테이너 제목 › 항목 이름 › 값 이름 나무 · 이름만 고침 · 키 · 필수/선택 · 꼴은 읽기만
- 필수 컨테이너 제목 = 정의 그룹 이름 자리 · 나머지 = 종류.이름 표(B05 · B06 그리기와 같은 차례)
- 미리보기 = B05 · B06 명세 폼에 고친 이름 · 빈 이름은 저장 막음 · [저장] = 새 판 + 이력(48장 길 그대로)
- 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점)는 명세 종류에서 숨김 · 명세 없는 종류는 그대로
- 시험: test_m02_so_def_spec(정의 사본) · ui_checks/52-11_정의_이름고치기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
2026-10-05 23:42:11 +09:00
co-authored by Claude Opus 5.5
parent 9f3a00041d
commit 103a207cee
9 changed files with 501 additions and 15 deletions
@@ -57,7 +57,8 @@ export interface StructDef {
구조물군: Record<string, string>;
동작: Record<string, string>;
types: TypeDef[];
종류: Record<string, { 기준측점?: boolean; 동작?: string[] }>;
/** 이름 = 구조물 명세 이름 바꿈 표(PLAN 52 · 키 `{컨테이너}[.{항목}[={값}]]`) */
종류: Record<string, { 기준측점?: boolean; 동작?: string[]; 이름?: Record<string, string> }>;
표시: Record<string, Record<string, false>>;
그룹: Record<string, GroupDef[]>;
배치: Record<string, Record<string, Spot>>;
@@ -257,6 +258,10 @@ export function problems(def: StructDef): string[] {
if (!o.choices?.length) out.push(`${t.name}.${o.label} 고르기 꼴은 선택지가 있어야 함`);
}
const grp = badNames((def.그룹[t.type_id] ?? []).map((g) => g.이름));
const spec = Object.entries(def.종류[t.type_id]?.이름 ?? {})
.filter(([, v]) => !v.trim() || v.length > NAME_MAX)
.map(([k]) => k);
if (spec.length) out.push(`${t.name} 명세 이름: ${spec.join(", ")}`);
if (grp.length) out.push(`${t.name} 그룹 이름: ${grp.join(", ")}`);
const lap = overlaps(placeAll(t.options, def.배치[t.type_id]));
if (lap.length) out.push(`${t.name} 배치 겹침: ${lap.join(", ")}`);