fix(card-frames): 카드 단위 엔티티 id v2 네임스페이스 발급 — 에디터에서 소실된 자식 엔티티 신규 생성 유도

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 00:01:25 +09:00
parent 675616bf51
commit b2693be111
2 changed files with 952 additions and 951 deletions

View File

@@ -211,7 +211,8 @@ const ALIGN_BOTTOM_CENTER = 6;
function guid(prefix, n) {
// 유효한 8-4-4-4-12 hex GUID 생성. prefix는 충돌 방지용 네임스페이스 바이트로 매핑.
const ns = prefix === 'hud' ? 0xd0 : prefix === 'dck' ? 0xca : prefix === 'cmb' ? 0xcb : prefix === 'rwd' ? 0xcc : prefix === 'map' ? 0xcd : prefix === 'shp' ? 0xce : prefix === 'rst' ? 0xcf : prefix === 'menu' ? 0xe0 : prefix === 'ins' ? 0xe1 : prefix === 'all' ? 0xe2 : prefix === 'trs' ? 0xe3 : prefix === 'job' ? 0xe4 : 0xfe;
const ns = prefix === 'hud' ? 0xd0 : prefix === 'dck' ? 0xca : prefix === 'cmb' ? 0xcb : prefix === 'rwd' ? 0xcc : prefix === 'map' ? 0xcd : prefix === 'shp' ? 0xce : prefix === 'rst' ? 0xcf : prefix === 'menu' ? 0xe0 : prefix === 'ins' ? 0xe1 : prefix === 'all' ? 0xe2 : prefix === 'trs' ? 0xe3 : prefix === 'job' ? 0xe4
: prefix === 'ins2' ? 0xe5 : prefix === 'all2' ? 0xe6 : prefix === 'rwd2' ? 0xe7 : prefix === 'shp2' ? 0xe8 : 0xfe;
const v = (ns * 0x100000 + n) >>> 0;
return `${v.toString(16).padStart(8, '0')}-0000-4000-8000-${v.toString(16).padStart(12, '0')}`;
}
@@ -794,13 +795,13 @@ function upsertUi() {
const INSPECT_CARD_COUNT = 60;
const INSPECT_CARD_W = 158;
const INSPECT_CARD_H = 214;
// id는 구 레이아웃(stride 7: root,Cost,Name,Desc,[NamePlate],[CostPlate],Art) 매핑 보존 —
// 같은 path가 항상 같은 id를 갖지 않으면 메이커 refresh의 id 기준 in-place 병합이 꼬 (P13 실측)
// 카드 단위 엔티티는 v2 네임스페이스(ins2/all2/rwd2/shp2) — 자식 구성이 바뀌면 id를 통째로 새로 발급해야 함.
// 구 id를 다른 path에 재사용하면 메이커 refresh의 id 기준 in-place 병합이 꼬여 자식이 소실됨 (P13 실측).
for (let i = 1; i <= INSPECT_CARD_COUNT; i++) {
const insBase = 6 + (i - 1) * 7;
const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`;
const card = entity({
id: guid('ins', insBase),
id: guid('ins2', insBase),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -817,7 +818,7 @@ function upsertUi() {
for (const [tIdx, [suffix, cfg]] of inspectLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) {
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
inspect.push(entity({
id: guid('ins', insBase + 1 + tIdx),
id: guid('ins2', insBase + 1 + tIdx),
path: `${cardPath}/${suffix}`,
modelId: 'uitext',
entryId: 'UIText',
@@ -831,7 +832,7 @@ function upsertUi() {
}));
}
inspect.push(entity({
id: guid('ins', insBase + 6),
id: guid('ins2', insBase + 6),
path: `${cardPath}/Art`,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -928,12 +929,12 @@ function upsertUi() {
const ALL_DECK_CARD_COUNT = 120;
const ALL_DECK_CARD_W = 158;
const ALL_DECK_CARD_H = 214;
// id 매핑 보존 (stride 7) — DeckInspectHud 주석 참조
// 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조
for (let i = 1; i <= ALL_DECK_CARD_COUNT; i++) {
const allBase = 6 + (i - 1) * 7;
const cardPath = `/ui/DefaultGroup/DeckAllHud/Grid/Card${i}`;
const card = entity({
id: guid('all', allBase),
id: guid('all2', allBase),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -950,7 +951,7 @@ function upsertUi() {
for (const [tIdx, [suffix, cfg]] of allDeckLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) {
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
allDeck.push(entity({
id: guid('all', allBase + 1 + tIdx),
id: guid('all2', allBase + 1 + tIdx),
path: `${cardPath}/${suffix}`,
modelId: 'uitext',
entryId: 'UIText',
@@ -964,7 +965,7 @@ function upsertUi() {
}));
}
allDeck.push(entity({
id: guid('all', allBase + 6),
id: guid('all2', allBase + 6),
path: `${cardPath}/Art`,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -1436,12 +1437,12 @@ function upsertUi() {
],
}));
const rewardXs = [-300, 0, 300];
// id 매핑 보존 (stride 7) — DeckInspectHud 주석 참조
// 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조
for (let i = 1; i <= 3; i++) {
const rwdBase = 2 + (i - 1) * 7;
const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`;
reward.push(entity({
id: guid('rwd', rwdBase),
id: guid('rwd2', rwdBase),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -1457,7 +1458,7 @@ function upsertUi() {
for (const [tIdx, [suffix, cfg]] of rewardLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]).entries()) {
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
reward.push(entity({
id: guid('rwd', rwdBase + 1 + tIdx),
id: guid('rwd2', rwdBase + 1 + tIdx),
path: `${cardPath}/${suffix}`,
modelId: 'uitext',
entryId: 'UIText',
@@ -1471,7 +1472,7 @@ function upsertUi() {
}));
}
reward.push(entity({
id: guid('rwd', rwdBase + 6),
id: guid('rwd2', rwdBase + 6),
path: `${cardPath}/Art`,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -1645,12 +1646,12 @@ function upsertUi() {
],
}));
const shopXs = [-300, 0, 300];
// id 매핑 보존 (stride 8: root,Cost,Name,Desc,Price,[NamePlate],[CostPlate],Art) — DeckInspectHud 주석 참조
// 카드 단위 엔티티 v2 네임스페이스 (stride 8: Price 포함) — DeckInspectHud 주석 참조
for (let i = 1; i <= 3; i++) {
const shpBase = 3 + (i - 1) * 8;
const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`;
shop.push(entity({
id: guid('shp', shpBase),
id: guid('shp2', shpBase),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
@@ -1668,7 +1669,7 @@ function upsertUi() {
for (const [tIdx, [suffix, cfg]] of shopTexts.entries()) {
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9;
shop.push(entity({
id: guid('shp', shpBase + 1 + tIdx),
id: guid('shp2', shpBase + 1 + tIdx),
path: `${cardPath}/${suffix}`,
modelId: 'uitext',
entryId: 'UIText',
@@ -1682,7 +1683,7 @@ function upsertUi() {
}));
}
shop.push(entity({
id: guid('shp', shpBase + 7),
id: guid('shp2', shpBase + 7),
path: `${cardPath}/Art`,
modelId: 'uisprite',
entryId: 'UISprite',

File diff suppressed because it is too large Load Diff