feat: auto interaction detection from real furniture data
CI / check (push) Successful in 31s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m17s

Replace classname/name keyword guessing with real data so sit/lay/stand
has zero false positives:
- autoDetectInteraction() now accepts real flags (cansiton/canlayon/
  canstandon), logicType, and animation-state count; real data wins and
  keywords are only a fallback when no real data exists.
- New furni-real-interaction helper reads the local FurnitureData.json
  (highest-revision entries win; a true flag in any duplicate is kept)
  and falls back to the official Habbo furnidata.
- interaction_modes_count now uses emulator conventions: dice=6,
  gate/teleport=2, roller=1, generic multistate = animation-state count,
  chairs/beds=1, plain items=0.
- verifyAndFixInteractionModesCount only touches items with real data
  and skips everything else (no keyword overwrites).
- parseNitroBundle falls back to gunzip for gzip-compressed .nitro files.
- Wired through furni-import, upload-import, clone-import, and the nitro
  editor auto-detect button.
This commit is contained in:
openhands committed 2026-08-19 20:28:24 +02:00
1 parent 121dda7249
commit 3fa192a108
8 files changed
+626 -78

No files matched your search

+16 -3
View File
@@ -16,6 +16,7 @@ import {
ensureDirectories,
getOrCreateCategoryPage,
} from "@/lib/services/furni-import";
import { nitroAnimationStatesCount } from "@/lib/services/furni-real-interaction";
import { browserHeaders } from "@/lib/services/import/core/browser-headers";
import {
curlDownload,
@@ -314,8 +315,12 @@ export async function cloneSingleFurni(params: {
}
}
// Validate it is a real Nitro bundle.
let clonedNitroJson: Record<string, unknown> | null = null;
try {
parseNitroBundle(await fs.readFile(/*turbopackIgnore: true*/ nitroPath));
const parsed = parseNitroBundle(
await fs.readFile(/*turbopackIgnore: true*/ nitroPath),
);
clonedNitroJson = parsed.json as Record<string, unknown>;
} catch {
await fs.unlink(/*turbopackIgnore: true*/ nitroPath).catch(() => {});
logger.warn("[clone-import] invalid .nitro bundle for", { classname });
@@ -367,8 +372,16 @@ export async function cloneSingleFurni(params: {
// allow_walk = canstandon, allow_sit = cansiton, allow_lay = canlayon.
const allowStack = stackHeight > 0 ? "1" : "0";
// Auto-detect interaction modes count from classname/name
const autoInteraction = autoDetectInteraction(classname, entry.name);
// Auto-detect interaction modes count. The source furnidata flags and the
// cloned .nitro animation states are real data — keyword guessing is only
// used when neither is available, so there are no false positives.
const autoInteraction = autoDetectInteraction(classname, entry.name, {
cansiton: entry.cansiton,
canlayon: entry.canlayon,
canstandon: entry.canstandon,
logicType: (clonedNitroJson?.logicType as string) || undefined,
animationStates: nitroAnimationStatesCount(clonedNitroJson),
});
const interactionModesCount = autoInteraction.interactionModesCount;
let newId: number;