Interaction detection: - Trust SWF-derived sit/lay/stand flags only when the logic XML actually contains action data (new hasActions metadata); otherwise fall back to keyword detection so custom furni without <action> nodes are still classified correctly - Add French/Dutch/German/Spanish/Italian keywords (chaise, banquette, stoel, silla, sedia, stuhl, tafel, mesa, ...) to sit/lay/stand detection with token-boundary matching to avoid false positives like bedside_table - Unify interaction_modes_count priority: mechanic fixed modes, then raw animation state count, then sit/lay fallback, then keyword default - Detect mechanic type even when real flags are not used Catalog integrity: - Skip duplicate catalog_items inserts in clone and upload imports - Re-check live catalog rows before applying generated repair SQL - Add rebuildCatalogOfferIds() which rebuilds every catalog_items.offer_id from the local FurnitureData.json (matched by entry id, then classname) and run it after every import path (single, batch, batch-regen, clone)
162 lines
4.5 KiB
TypeScript
162 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
autoDetectInteraction,
|
|
nitroAnimationStatesCount,
|
|
} from "./auto-interaction";
|
|
|
|
describe("autoDetectInteraction with real data", () => {
|
|
it("trusts real flags over keywords for sit", () => {
|
|
const result = autoDetectInteraction("chair_plasto", "Chair", {
|
|
cansiton: true,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
});
|
|
expect(result.canSit).toBe(true);
|
|
expect(result.canLay).toBe(false);
|
|
expect(result.canStand).toBe(false);
|
|
expect(result.interactionModesCount).toBe(1);
|
|
expect(result.reason).toContain("Real furniture data");
|
|
});
|
|
|
|
it("trusts real flags over keywords for lay", () => {
|
|
const result = autoDetectInteraction("bed_silo_two", "Bed", {
|
|
cansiton: false,
|
|
canlayon: true,
|
|
canstandon: false,
|
|
});
|
|
expect(result.canSit).toBe(false);
|
|
expect(result.canLay).toBe(true);
|
|
expect(result.interactionModesCount).toBe(1);
|
|
expect(result.interactionType).toBe("bed");
|
|
});
|
|
|
|
it("respects curated all-false real flags (no keyword false positive)", () => {
|
|
const result = autoDetectInteraction("table_plasto_4leg", "Table", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
hasActionData: true,
|
|
});
|
|
expect(result.canSit).toBe(false);
|
|
expect(result.canLay).toBe(false);
|
|
expect(result.canStand).toBe(false);
|
|
expect(result.interactionModesCount).toBe(0);
|
|
});
|
|
|
|
it("falls back to keywords when flags carry no action data", () => {
|
|
const result = autoDetectInteraction("chaise_metal_lamarr", "Chaise", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
animationStates: 4,
|
|
});
|
|
expect(result.canSit).toBe(true);
|
|
expect(result.canStand).toBe(false);
|
|
expect(result.interactionModesCount).toBe(4);
|
|
});
|
|
|
|
it("detects French bench (banquette) with raw state count", () => {
|
|
const result = autoDetectInteraction("banquette_lamarr", "Banquette", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
animationStates: 6,
|
|
});
|
|
expect(result.canSit).toBe(true);
|
|
expect(result.interactionType).toBe("bench");
|
|
expect(result.interactionModesCount).toBe(6);
|
|
});
|
|
|
|
it("does not match short keywords without token boundaries", () => {
|
|
const result = autoDetectInteraction("bedside_table", "Bedside Table", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
});
|
|
expect(result.canSit).toBe(false);
|
|
expect(result.canLay).toBe(false);
|
|
expect(result.interactionType).toBe("table");
|
|
});
|
|
|
|
it("detects dice mechanics from classname with fixed modes=6", () => {
|
|
const result = autoDetectInteraction("dice", "Dice", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
logicType: "furniture_multistate",
|
|
animationStates: 7,
|
|
});
|
|
expect(result.interactionType).toBe("dice");
|
|
expect(result.interactionModesCount).toBe(6);
|
|
});
|
|
|
|
it("detects teleport mechanics with fixed modes=2", () => {
|
|
const result = autoDetectInteraction("teleport_door", "Teleport", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: true,
|
|
logicType: "furniture_multistate",
|
|
animationStates: 3,
|
|
});
|
|
expect(result.interactionType).toBe("teleport");
|
|
expect(result.interactionModesCount).toBe(2);
|
|
});
|
|
|
|
it("uses real animation states for generic multistate items", () => {
|
|
const result = autoDetectInteraction("sleepy_24_block_01", "", {
|
|
cansiton: false,
|
|
canlayon: false,
|
|
canstandon: false,
|
|
animationStates: 13,
|
|
});
|
|
expect(result.interactionModesCount).toBe(13);
|
|
});
|
|
|
|
it("still keyword-falls-back when no real data is present", () => {
|
|
const result = autoDetectInteraction("chair_plasto", "Plasto Chair");
|
|
expect(result.canSit).toBe(true);
|
|
expect(result.interactionModesCount).toBe(1);
|
|
expect(result.reason).toContain("chair");
|
|
});
|
|
});
|
|
|
|
describe("nitroAnimationStatesCount", () => {
|
|
it("counts dice-style states", () => {
|
|
expect(
|
|
nitroAnimationStatesCount({
|
|
visualizations: [
|
|
{
|
|
animations: {
|
|
"0": {},
|
|
"1": {},
|
|
"2": {},
|
|
"3": {},
|
|
"4": {},
|
|
"5": {},
|
|
"6": {},
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
).toBe(7);
|
|
});
|
|
|
|
it("returns undefined for basic items without animations", () => {
|
|
expect(
|
|
nitroAnimationStatesCount({ visualizations: [{ animations: {} }] }),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("ignores jukebox track states above 99", () => {
|
|
expect(
|
|
nitroAnimationStatesCount({
|
|
visualizations: [{ animations: { "0": {}, "1": {}, "100": {} } }],
|
|
}),
|
|
).toBe(2);
|
|
});
|
|
|
|
it("returns undefined for null input", () => {
|
|
expect(nitroAnimationStatesCount(null)).toBeUndefined();
|
|
});
|
|
});
|