feat: auto-repair sprite_id drift after every import
The emulator sends items_base.sprite_id to the client and Nitro resolves the furniture by looking up that id in FurnitureData.json. Legacy rows where sprite_id drifted from the id made the client render a completely different item (e.g. a wired or custom asset instead of the purchased furniture). Add verifyAndFixSpriteIds() which repairs any row whose sprite_id no longer matches its id while the local furnidata carries the item under that id, and run it after every import path (single, batch, batch-regen, clone) next to the offer_id rebuild.
This commit is contained in:
1 parent
92b0ba3b18
commit
984b5bf793
5 files changed
+84
No files matched your search
@@ -12,6 +12,7 @@ import {
|
|||||||
appendFurniEntry,
|
appendFurniEntry,
|
||||||
rebuildCatalogOfferIds,
|
rebuildCatalogOfferIds,
|
||||||
reconcileFurniDataWithItemsBase,
|
reconcileFurniDataWithItemsBase,
|
||||||
|
verifyAndFixSpriteIds,
|
||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
ensureFurniOwnership,
|
ensureFurniOwnership,
|
||||||
@@ -144,6 +145,7 @@ export const POST = withAdmin(
|
|||||||
await reconcileImportedOfferIds();
|
await reconcileImportedOfferIds();
|
||||||
await reconcileFurniDataWithItemsBase();
|
await reconcileFurniDataWithItemsBase();
|
||||||
await rebuildCatalogOfferIds();
|
await rebuildCatalogOfferIds();
|
||||||
|
await verifyAndFixSpriteIds();
|
||||||
await verifyAndFixInteractionModesCount();
|
await verifyAndFixInteractionModesCount();
|
||||||
await ensureFurniOwnership();
|
await ensureFurniOwnership();
|
||||||
await syncAssetsToGamedataBundle();
|
await syncAssetsToGamedataBundle();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
readFurniData,
|
readFurniData,
|
||||||
rebuildCatalogOfferIds,
|
rebuildCatalogOfferIds,
|
||||||
reconcileFurniDataWithItemsBase,
|
reconcileFurniDataWithItemsBase,
|
||||||
|
verifyAndFixSpriteIds,
|
||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
downloadFile,
|
downloadFile,
|
||||||
@@ -273,6 +274,9 @@ export const POST = withAdmin(
|
|||||||
// Rebuild every catalog offer_id from the furnidata
|
// Rebuild every catalog offer_id from the furnidata
|
||||||
const offerRebuild = await rebuildCatalogOfferIds();
|
const offerRebuild = await rebuildCatalogOfferIds();
|
||||||
|
|
||||||
|
// Repair any sprite_id drift so the client renders the right item
|
||||||
|
const spriteVerify = await verifyAndFixSpriteIds();
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: "regen_complete",
|
type: "regen_complete",
|
||||||
succeeded,
|
succeeded,
|
||||||
@@ -287,6 +291,8 @@ export const POST = withAdmin(
|
|||||||
furniDataConflicts: furniReconcile.conflicts,
|
furniDataConflicts: furniReconcile.conflicts,
|
||||||
offerRebuildChecked: offerRebuild.checked,
|
offerRebuildChecked: offerRebuild.checked,
|
||||||
offerRebuildFixed: offerRebuild.fixed,
|
offerRebuildFixed: offerRebuild.fixed,
|
||||||
|
spriteChecked: spriteVerify.checked,
|
||||||
|
spriteFixed: spriteVerify.fixed,
|
||||||
duration: Date.now() - startTime,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
appendFurniEntriesBatch,
|
appendFurniEntriesBatch,
|
||||||
rebuildCatalogOfferIds,
|
rebuildCatalogOfferIds,
|
||||||
reconcileFurniDataWithItemsBase,
|
reconcileFurniDataWithItemsBase,
|
||||||
|
verifyAndFixSpriteIds,
|
||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
ensureDirectories,
|
ensureDirectories,
|
||||||
@@ -229,6 +230,7 @@ export const POST = withAdmin(
|
|||||||
let furniDataMissing = 0;
|
let furniDataMissing = 0;
|
||||||
let furniDataConflicts = 0;
|
let furniDataConflicts = 0;
|
||||||
let offerRebuildFixed = 0;
|
let offerRebuildFixed = 0;
|
||||||
|
let spriteFixed = 0;
|
||||||
let interactionFixed = 0;
|
let interactionFixed = 0;
|
||||||
try {
|
try {
|
||||||
offerIdsFixed = (await reconcileImportedOfferIds()).fixed;
|
offerIdsFixed = (await reconcileImportedOfferIds()).fixed;
|
||||||
@@ -242,6 +244,7 @@ export const POST = withAdmin(
|
|||||||
furniDataMissing = furniReconcile.missing;
|
furniDataMissing = furniReconcile.missing;
|
||||||
furniDataConflicts = furniReconcile.conflicts;
|
furniDataConflicts = furniReconcile.conflicts;
|
||||||
offerRebuildFixed = (await rebuildCatalogOfferIds()).fixed;
|
offerRebuildFixed = (await rebuildCatalogOfferIds()).fixed;
|
||||||
|
spriteFixed = (await verifyAndFixSpriteIds()).fixed;
|
||||||
interactionFixed = (await verifyAndFixInteractionModesCount()).fixed;
|
interactionFixed = (await verifyAndFixInteractionModesCount()).fixed;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -264,6 +267,7 @@ export const POST = withAdmin(
|
|||||||
furniDataMissing,
|
furniDataMissing,
|
||||||
furniDataConflicts,
|
furniDataConflicts,
|
||||||
offerRebuildFixed,
|
offerRebuildFixed,
|
||||||
|
spriteFixed,
|
||||||
interactionFixed,
|
interactionFixed,
|
||||||
duration: Date.now() - startTime,
|
duration: Date.now() - startTime,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
|||||||
import {
|
import {
|
||||||
rebuildCatalogOfferIds,
|
rebuildCatalogOfferIds,
|
||||||
reconcileFurniDataWithItemsBase,
|
reconcileFurniDataWithItemsBase,
|
||||||
|
verifyAndFixSpriteIds,
|
||||||
} from "@/lib/services/furni-data";
|
} from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
autoPriceFurni,
|
autoPriceFurni,
|
||||||
@@ -175,6 +176,7 @@ export const GET = withAdmin(
|
|||||||
if (action === "live-reconcile") {
|
if (action === "live-reconcile") {
|
||||||
const furniReconcile = await reconcileFurniDataWithItemsBase();
|
const furniReconcile = await reconcileFurniDataWithItemsBase();
|
||||||
const offerRebuild = await rebuildCatalogOfferIds();
|
const offerRebuild = await rebuildCatalogOfferIds();
|
||||||
|
const spriteVerify = await verifyAndFixSpriteIds();
|
||||||
return apiOk({
|
return apiOk({
|
||||||
furniDataFixedIds: furniReconcile.fixedIds,
|
furniDataFixedIds: furniReconcile.fixedIds,
|
||||||
furniDataFixedOfferIds: furniReconcile.fixedOfferIds,
|
furniDataFixedOfferIds: furniReconcile.fixedOfferIds,
|
||||||
@@ -182,6 +184,8 @@ export const GET = withAdmin(
|
|||||||
furniDataConflicts: furniReconcile.conflicts,
|
furniDataConflicts: furniReconcile.conflicts,
|
||||||
offerRebuildChecked: offerRebuild.checked,
|
offerRebuildChecked: offerRebuild.checked,
|
||||||
offerRebuildFixed: offerRebuild.fixed,
|
offerRebuildFixed: offerRebuild.fixed,
|
||||||
|
spriteChecked: spriteVerify.checked,
|
||||||
|
spriteFixed: spriteVerify.fixed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,6 +465,9 @@ export const POST = withAdmin(
|
|||||||
// Rebuild every catalog offer_id from the furnidata
|
// Rebuild every catalog offer_id from the furnidata
|
||||||
const offerRebuild = await rebuildCatalogOfferIds();
|
const offerRebuild = await rebuildCatalogOfferIds();
|
||||||
|
|
||||||
|
// Repair any sprite_id drift so the client renders the right item
|
||||||
|
const spriteVerify = await verifyAndFixSpriteIds();
|
||||||
|
|
||||||
// Verify + fix interaction_modes_count once for the whole batch
|
// Verify + fix interaction_modes_count once for the whole batch
|
||||||
const verification = await verifyAndFixInteractionModesCount();
|
const verification = await verifyAndFixInteractionModesCount();
|
||||||
|
|
||||||
@@ -482,6 +489,8 @@ export const POST = withAdmin(
|
|||||||
furniDataConflicts: furniReconcile.conflicts,
|
furniDataConflicts: furniReconcile.conflicts,
|
||||||
offerRebuildChecked: offerRebuild.checked,
|
offerRebuildChecked: offerRebuild.checked,
|
||||||
offerRebuildFixed: offerRebuild.fixed,
|
offerRebuildFixed: offerRebuild.fixed,
|
||||||
|
spriteChecked: spriteVerify.checked,
|
||||||
|
spriteFixed: spriteVerify.fixed,
|
||||||
interactionFixed: verification.fixed,
|
interactionFixed: verification.fixed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -547,6 +556,9 @@ export const POST = withAdmin(
|
|||||||
// Rebuild every catalog offer_id from the furnidata
|
// Rebuild every catalog offer_id from the furnidata
|
||||||
const offerRebuild = await rebuildCatalogOfferIds();
|
const offerRebuild = await rebuildCatalogOfferIds();
|
||||||
|
|
||||||
|
// Repair any sprite_id drift so the client renders the right item
|
||||||
|
const spriteVerify = await verifyAndFixSpriteIds();
|
||||||
|
|
||||||
// Verify + fix interaction_modes_count (single consolidated pass)
|
// Verify + fix interaction_modes_count (single consolidated pass)
|
||||||
const verification = await verifyAndFixInteractionModesCount();
|
const verification = await verifyAndFixInteractionModesCount();
|
||||||
|
|
||||||
@@ -580,6 +592,8 @@ export const POST = withAdmin(
|
|||||||
furniDataConflicts: furniReconcile.conflicts,
|
furniDataConflicts: furniReconcile.conflicts,
|
||||||
offerRebuildChecked: offerRebuild.checked,
|
offerRebuildChecked: offerRebuild.checked,
|
||||||
offerRebuildFixed: offerRebuild.fixed,
|
offerRebuildFixed: offerRebuild.fixed,
|
||||||
|
spriteChecked: spriteVerify.checked,
|
||||||
|
spriteFixed: spriteVerify.fixed,
|
||||||
interactionFixed: verification.fixed,
|
interactionFixed: verification.fixed,
|
||||||
spriteCount: result.spriteCount,
|
spriteCount: result.spriteCount,
|
||||||
warnings: result.warnings,
|
warnings: result.warnings,
|
||||||
|
|||||||
@@ -656,3 +656,61 @@ export async function rebuildCatalogOfferIds(): Promise<CatalogOfferRebuildResul
|
|||||||
return { checked: catalogRows.length, fixed, fallback };
|
return { checked: catalogRows.length, fixed, fallback };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SpriteIdVerifyResult {
|
||||||
|
/** items_base rows examined. */
|
||||||
|
checked: number;
|
||||||
|
/** Rows whose sprite_id was corrected to their id. */
|
||||||
|
fixed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that every items_base.sprite_id matches its id whenever the local
|
||||||
|
* FurnitureData.json carries the item under that id.
|
||||||
|
*
|
||||||
|
* The emulator sends sprite_id to the client and Nitro resolves the furniture
|
||||||
|
* by looking up that id in the furnidata — a stale sprite_id makes the client
|
||||||
|
* render a completely different item (e.g. a wired or a custom in place of a
|
||||||
|
* seaside sofa). Imports always write sprite_id = id, but legacy rows may
|
||||||
|
* drift; this pass repairs them after every import.
|
||||||
|
*/
|
||||||
|
export async function verifyAndFixSpriteIds(): Promise<SpriteIdVerifyResult> {
|
||||||
|
return withFurniDataLock(async () => {
|
||||||
|
const furniData = (await readFurniData()) as Record<
|
||||||
|
string,
|
||||||
|
{ furnitype?: Array<Record<string, unknown>> }
|
||||||
|
>;
|
||||||
|
const classById = new Map<number, Set<string>>();
|
||||||
|
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
||||||
|
for (const e of furniData[section]?.furnitype ?? []) {
|
||||||
|
const entryId = Number(e?.id);
|
||||||
|
const cn = typeof e?.classname === "string" ? e.classname : "";
|
||||||
|
if (!Number.isFinite(entryId) || !cn) continue;
|
||||||
|
const set = classById.get(entryId) ?? new Set<string>();
|
||||||
|
set.add(cn);
|
||||||
|
classById.set(entryId, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: ItemsBase.id,
|
||||||
|
spriteId: ItemsBase.spriteId,
|
||||||
|
itemName: ItemsBase.itemName,
|
||||||
|
})
|
||||||
|
.from(ItemsBase);
|
||||||
|
|
||||||
|
let fixed = 0;
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.spriteId === row.id) continue;
|
||||||
|
const entries = classById.get(row.id);
|
||||||
|
if (!entries?.has(row.itemName)) continue;
|
||||||
|
await db
|
||||||
|
.update(ItemsBase)
|
||||||
|
.set({ spriteId: row.id })
|
||||||
|
.where(eq(ItemsBase.id, row.id));
|
||||||
|
fixed++;
|
||||||
|
}
|
||||||
|
return { checked: rows.length, fixed };
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user