feat(monster): 적 종별 모델 생성기 + 모델 산출물 재생성(9종)

단일 소스 data/enemies.json appearance → Models/Monsters/<enemyId>.model.
EntryKey model://monster-<id> 네임스페이스(기존 모델과 충돌 가드), 태생 AI-free.
appearance 미확보 9종(slime 등)은 Task 2(RUID 수확) 후 추가 예정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
This commit is contained in:
2026-07-02 13:52:45 +09:00
parent cdfc79cd57
commit 1390b9ec50
10 changed files with 2100 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { buildMonsterModel, modelEntryId } from './lib/monster-model.mjs';
// 적 18종 각각의 전용 모델(.model) emit. 단일 소스: data/enemies.json(appearance) + ChaseMonster.model(골격).
const OUT_DIR = 'RootDesk/MyDesk/Models/Monsters';
const enemies = JSON.parse(readFileSync('data/enemies.json', 'utf8')).enemies;
const skeleton = JSON.parse(readFileSync('Global/ChaseMonster.model', 'utf8'));
// EntryKey 충돌 가드 (LEA-3015 예방): 기존 .model들의 EntryKey 수집 (경로별)
const existing = []; // { key, path }
for (const dir of ['Global', OUT_DIR]) {
for (const f of readdirSync(dir).filter((n) => n.endsWith('.model'))) {
const path = `${dir}/${f}`;
existing.push({ key: JSON.parse(readFileSync(path, 'utf8')).EntryKey, path });
}
}
const written = [];
const skipped = [];
for (const [enemyId, enemy] of Object.entries(enemies)) {
if (!enemy.appearance) { skipped.push(enemyId); continue; }
const file = buildMonsterModel(enemyId, enemy, skeleton);
const outPath = `${OUT_DIR}/${enemyId}.model`;
const clash = existing.find((e) => e.key === file.EntryKey && e.path !== outPath);
if (clash) throw new Error(`[gen-monster-models] EntryKey 충돌: ${file.EntryKey} (기존 ${clash.path})`);
writeFileSync(outPath, JSON.stringify(file, null, 2) + '\n', 'utf8');
written.push(enemyId);
}
console.log(`[gen-monster-models] ${written.length}종 emit${skipped.length ? ` / appearance 없음 스킵: ${skipped.join(', ')}` : ''}`);