diff --git a/src/app/admin/import/audit/audit-client.tsx b/src/app/admin/import/audit/audit-client.tsx
index cc3e6f92..ae2817dc 100644
--- a/src/app/admin/import/audit/audit-client.tsx
+++ b/src/app/admin/import/audit/audit-client.tsx
@@ -86,7 +86,11 @@ interface AuditEvent {
sqlCount?: number;
applied?: number;
sqlFailed?: number;
- furniData?: { added: number; removedDuplicates: number };
+ furniData?: {
+ added: number;
+ removedDuplicates: number;
+ removedIdConflicts: number;
+ };
structure?: {
orphanedRemoved: number;
orphanedCleaned: number;
@@ -175,6 +179,7 @@ export function AuditClient() {
const [furniDataResult, setFurniDataResult] = useState<{
added: number;
removedDuplicates: number;
+ removedIdConflicts: number;
} | null>(null);
const [structureResult, setStructureResult] = useState<{
orphanedRemoved: number;
@@ -675,6 +680,15 @@ export function AuditClient() {
>
)}
+ {furniDataResult.removedIdConflicts > 0 && (
+ <>
+ {" · "}
+
+ {furniDataResult.removedIdConflicts} spriteId conflicts
+ fixed
+
+ >
+ )}
)}
diff --git a/src/lib/services/catalog-audit.ts b/src/lib/services/catalog-audit.ts
index d3bfd7dc..18b3ed13 100644
--- a/src/lib/services/catalog-audit.ts
+++ b/src/lib/services/catalog-audit.ts
@@ -56,7 +56,11 @@ export interface AuditEvent {
sqlCount?: number;
applied?: number;
sqlFailed?: number;
- furniData?: { added: number; removedDuplicates: number };
+ furniData?: {
+ added: number;
+ removedDuplicates: number;
+ removedIdConflicts: number;
+ };
structure?: {
orphanedRemoved: number;
orphanedCleaned: number;
@@ -715,6 +719,7 @@ export async function runCatalogAudit(
furniData: {
added: result.added,
removedDuplicates: result.removedDuplicates,
+ removedIdConflicts: result.removedIdConflicts,
},
});
} catch (err) {
diff --git a/src/lib/services/catalog-repair.ts b/src/lib/services/catalog-repair.ts
index 832f7017..20c74d34 100644
--- a/src/lib/services/catalog-repair.ts
+++ b/src/lib/services/catalog-repair.ts
@@ -620,6 +620,7 @@ export async function repairDuplicateClassnames(): Promise,
): Promise {
- if (items.length === 0) return { added: 0, removedDuplicates: 0 };
+ if (items.length === 0)
+ return { added: 0, removedDuplicates: 0, removedIdConflicts: 0 };
return withFurniDataLock(async () => {
const furniData = (await readFurniData()) as Record<
@@ -697,7 +700,33 @@ export async function repairFurniData(
}
}
- // 2) Add entries for items_base rows missing from the file.
+ // 2) Resolve spriteId conflicts (same id used by different classnames) — keep first, remove others.
+ let removedIdConflicts = 0;
+ for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
+ const sec = furniData[section];
+ if (!sec?.furnitype) continue;
+ const idToClassname = new Map();
+ const deduped = sec.furnitype.filter((e) => {
+ const id = Number(e?.id);
+ const classname = typeof e.classname === "string" ? e.classname : "";
+ if (!Number.isFinite(id) || id <= 0 || !classname) return true;
+ const existing = idToClassname.get(id);
+ if (existing && existing !== classname) {
+ // Conflict: same id used by different classname — keep first, remove this
+ removedIdConflicts++;
+ return false;
+ }
+ if (!existing) {
+ idToClassname.set(id, classname);
+ }
+ return true;
+ });
+ if (deduped.length !== sec.furnitype.length) {
+ furniData[section].furnitype = deduped;
+ }
+ }
+
+ // 3) Add entries for items_base rows missing from the file.
const existing = new Set();
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
for (const e of furniData[section]?.furnitype ?? []) {
@@ -714,8 +743,8 @@ export async function repairFurniData(
});
}
- // 3) Write once if anything changed.
- if (removedDuplicates > 0 || added.length > 0) {
+ // 4) Write once if anything changed.
+ if (removedDuplicates > 0 || removedIdConflicts > 0 || added.length > 0) {
for (const { entry, itemType } of added) {
const sectionKey = itemType === "i" ? "wallitemtypes" : "roomitemtypes";
if (!furniData[sectionKey]) {
@@ -725,6 +754,6 @@ export async function repairFurniData(
}
await writeFurniData(furniData as Record);
}
- return { added: added.length, removedDuplicates };
+ return { added: added.length, removedDuplicates, removedIdConflicts };
});
}