@hypersoniclabs/helix-cli 0.1.7 → 0.1.24-helix3.438
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +631 -8
- package/assets/bridge/metahuman-eye-baked.png +0 -0
- package/assets/item-def-sounds/Common/A_DryShot.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_CloseCoverA.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_CloseCoverB.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_InsertMag.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_RemoveMag.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_ChargingHandle.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_01.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_02.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_03.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_InsertMag.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_RemoveMag.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_003.ogg +0 -0
- package/bridge-plugins/helix.bridge.unreal/bin/unreal-skeletal-mesh-extract.js +812 -0
- package/bridge-plugins/helix.bridge.unreal/bridge-plugin.json +40 -0
- package/bridge-plugins/helix.bridge.unreal/test/exporter.test.js +418 -0
- package/bridge-plugins/helix.bridge.unreal/test/handler.test.js +417 -0
- package/bridge-plugins/helix.bridge.unreal/unreal/export_skeletal_mesh.py +384 -0
- package/bridge-recipes/cyberpunk/cyberpunk-female-bone-map.json +476 -0
- package/bridge-recipes/sims4/README.md +43 -0
- package/bridge-recipes/sims4/sims4-female-bone-map.json +65 -0
- package/bridge-recipes/unreal/character-customizer/README.md +58 -0
- package/bridge-recipes/unreal/character-customizer/all.json +13 -0
- package/bridge-recipes/unreal/character-customizer/base-male-body.json +6 -0
- package/bridge-recipes/unreal/character-customizer/base-male-head.json +6 -0
- package/bridge-recipes/unreal/character-customizer/beard.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/beard.json +6 -0
- package/bridge-recipes/unreal/character-customizer/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/bottom.json +6 -0
- package/bridge-recipes/unreal/character-customizer/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/eyewear.json +6 -0
- package/bridge-recipes/unreal/character-customizer/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/footwear.json +6 -0
- package/bridge-recipes/unreal/character-customizer/hair.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/hair.json +6 -0
- package/bridge-recipes/unreal/character-customizer/test/recipes.test.js +74 -0
- package/bridge-recipes/unreal/character-customizer/top.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/top.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-female/README.md +57 -0
- package/bridge-recipes/unreal/helix-character-creator-female/all.json +18 -0
- package/bridge-recipes/unreal/helix-character-creator-female/back.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/back.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/base-female-face.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/base-female.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/bottom.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/eyewear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-basic-t-shirt.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-basic-t-shirt.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-hair.json +23 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-nebula-urbanghost.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-singles.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch5.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch6.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch7.json +11 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cargo-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-carnival-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-carnival-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-chain.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-chain.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-hole-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-hole-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-patch.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-patch.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-firefight-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-firefight-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-sneakers.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-sneakers.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-gamer-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-gamer-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-guy-fawkes-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-guy-fawkes-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a10.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a10.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a11.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a11.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a12.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a12.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a13.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a13.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a14.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a14.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a15.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a15.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a16.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a16.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a17.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a17.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a18.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a18.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a19.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a19.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a20.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a20.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a21.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a21.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a4.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a4.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a5.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a5.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a7.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a7.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a8.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a8.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a9.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a9.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-high-waist-tailored-trousers.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-high-waist-tailored-trousers.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hogwarts-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hogwarts-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hoody.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hoody.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-jacket-6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-jacket-6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-joker-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-joker-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes-brown.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes-brown.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-marshmallow-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-marshmallow-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-maska.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-maska.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-ninza-costume.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-ninza-costume.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-orange-kombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-orange-kombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panama.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panama.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panda-bag-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panda-bag-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pantslow.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pantslow.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-paper-bag-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-paper-bag-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pig-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pig-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-police-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-police-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-prison-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-prison-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-b4low.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-b4low.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-swimming.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-swimming.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shortlow.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shortlow.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shorts-b3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shorts-b3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-sterile-gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-sterile-gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-surgeon-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-surgeon-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-swat.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-swat.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-topx1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-topx1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-warehouse-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-warehouse-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wide-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wide-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wolf-set.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wolf-set.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-woman-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-woman-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-women-out-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-women-out-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/footwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/hair.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/hair.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/headwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/headwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/neck.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/neck.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/README.md +15 -0
- package/bridge-recipes/unreal/helix-character-creator-male/all.json +18 -0
- package/bridge-recipes/unreal/helix-character-creator-male/back.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/back.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/base-male-face.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/base-male.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/bottom.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/eyewear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/footwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/hair.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/hair.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/headwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/headwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-hair.json +23 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-nebula-urbanghost.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-uniforms.json +10 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch6-slot2.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch7-slot3.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch8-combined.json +11 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-boots-25.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-cargo-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-diamond-hands.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-diamond-hands.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-doctor-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-doctor-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-patch.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-patch.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-puffy.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-puffy.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-firefight-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-firefight-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-gamer-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-gamer-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a10.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a10.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a11.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a11.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a12.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a12.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a13.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a13.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a14.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a14.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a15.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a15.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a16.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a16.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a17.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a17.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a18.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a18.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a19.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a19.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a20.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a20.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a21.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a21.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a4.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a4.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a5.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a5.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a7.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a7.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a8.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a8.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a9.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a9.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown-v2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown-v2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-man-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-mikejack-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-orangekombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-orangekombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-out2-boots.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-out2-boots.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-panda-bag-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-panda-bag-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pants-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pants-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-police-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-police-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-prison-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-prison-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-robber-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-robber-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-short-swimming.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-short-swimming.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sip-cap.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sip-cap.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-socks.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-socks.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-warehouse-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-warehouse-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/neck.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/neck.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/palette-pilot.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/test/recipes.test.js +122 -0
- package/bridge-recipes/unreal/helix-character-creator-male/top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/bottom-nopalette.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/headwear-nopalette.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/neck-nopalette.addon.json +1 -0
- package/dist/ability.d.ts +2 -2
- package/dist/ability.js +6 -5
- package/dist/ability.js.map +1 -1
- package/dist/achievement.d.ts +123 -0
- package/dist/achievement.js +400 -0
- package/dist/achievement.js.map +1 -0
- package/dist/api.d.ts +19 -1
- package/dist/api.js +80 -6
- package/dist/api.js.map +1 -1
- package/dist/assets.d.ts +595 -0
- package/dist/assets.js +1121 -0
- package/dist/assets.js.map +1 -0
- package/dist/audioCommand.d.ts +65 -0
- package/dist/audioCommand.js +181 -0
- package/dist/audioCommand.js.map +1 -0
- package/dist/bakeBodyRegionsCli.d.ts +19 -0
- package/dist/bakeBodyRegionsCli.js +25 -0
- package/dist/bakeBodyRegionsCli.js.map +1 -0
- package/dist/bakeLighting.d.ts +173 -0
- package/dist/bakeLighting.js +452 -0
- package/dist/bakeLighting.js.map +1 -0
- package/dist/bridge/bridge.testutil.d.ts +105 -0
- package/dist/bridge/bridge.testutil.js +259 -0
- package/dist/bridge/bridge.testutil.js.map +1 -0
- package/dist/bridge/characterAddon.d.ts +47 -0
- package/dist/bridge/characterAddon.js +146 -0
- package/dist/bridge/characterAddon.js.map +1 -0
- package/dist/bridge/detect.d.ts +4 -0
- package/dist/bridge/detect.js +154 -0
- package/dist/bridge/detect.js.map +1 -0
- package/dist/bridge/execute.d.ts +3 -0
- package/dist/bridge/execute.js +206 -0
- package/dist/bridge/execute.js.map +1 -0
- package/dist/bridge/glb.d.ts +60 -0
- package/dist/bridge/glb.js +81 -0
- package/dist/bridge/glb.js.map +1 -0
- package/dist/bridge/hash.d.ts +6 -0
- package/dist/bridge/hash.js +40 -0
- package/dist/bridge/hash.js.map +1 -0
- package/dist/bridge/index.d.ts +11 -0
- package/dist/bridge/index.js +28 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/internal/artifacts.d.ts +5 -0
- package/dist/bridge/internal/artifacts.js +31 -0
- package/dist/bridge/internal/artifacts.js.map +1 -0
- package/dist/bridge/internal/builtInExecution.d.ts +4 -0
- package/dist/bridge/internal/builtInExecution.js +514 -0
- package/dist/bridge/internal/builtInExecution.js.map +1 -0
- package/dist/bridge/internal/externalExecution.d.ts +12 -0
- package/dist/bridge/internal/externalExecution.js +261 -0
- package/dist/bridge/internal/externalExecution.js.map +1 -0
- package/dist/bridge/internal/outputReservation.d.ts +13 -0
- package/dist/bridge/internal/outputReservation.js +151 -0
- package/dist/bridge/internal/outputReservation.js.map +1 -0
- package/dist/bridge/internal/snapshots.d.ts +32 -0
- package/dist/bridge/internal/snapshots.js +562 -0
- package/dist/bridge/internal/snapshots.js.map +1 -0
- package/dist/bridge/management.d.ts +40 -0
- package/dist/bridge/management.js +333 -0
- package/dist/bridge/management.js.map +1 -0
- package/dist/bridge/manifest.d.ts +8 -0
- package/dist/bridge/manifest.js +192 -0
- package/dist/bridge/manifest.js.map +1 -0
- package/dist/bridge/metahuman.d.ts +26 -0
- package/dist/bridge/metahuman.js +332 -0
- package/dist/bridge/metahuman.js.map +1 -0
- package/dist/bridge/planner.d.ts +9 -0
- package/dist/bridge/planner.js +246 -0
- package/dist/bridge/planner.js.map +1 -0
- package/dist/bridge/plugins.d.ts +17 -0
- package/dist/bridge/plugins.js +307 -0
- package/dist/bridge/plugins.js.map +1 -0
- package/dist/bridge/resolution.d.ts +8 -0
- package/dist/bridge/resolution.js +55 -0
- package/dist/bridge/resolution.js.map +1 -0
- package/dist/bridge/types.d.ts +247 -0
- package/dist/bridge/types.js +60 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/validate.d.ts +12 -0
- package/dist/bridge/validate.js +370 -0
- package/dist/bridge/validate.js.map +1 -0
- package/dist/bridge/zipSource.d.ts +27 -0
- package/dist/bridge/zipSource.js +90 -0
- package/dist/bridge/zipSource.js.map +1 -0
- package/dist/browser/hostSlots.d.ts +57 -0
- package/dist/browser/hostSlots.js +371 -0
- package/dist/browser/hostSlots.js.map +1 -0
- package/dist/browser/launch.d.ts +25 -0
- package/dist/browser/launch.js +74 -0
- package/dist/browser/launch.js.map +1 -0
- package/dist/browser/nonSystemChrome.d.ts +15 -0
- package/dist/browser/nonSystemChrome.js +91 -0
- package/dist/browser/nonSystemChrome.js.map +1 -0
- package/dist/builds/index.d.ts +54 -0
- package/dist/builds/index.js +215 -0
- package/dist/builds/index.js.map +1 -0
- package/dist/bundle.d.ts +35 -2
- package/dist/bundle.js +437 -20
- package/dist/bundle.js.map +1 -1
- package/dist/canary/cli.d.ts +17 -0
- package/dist/canary/cli.js +95 -0
- package/dist/canary/cli.js.map +1 -0
- package/dist/canary/index.d.ts +4 -0
- package/dist/canary/index.js +21 -0
- package/dist/canary/index.js.map +1 -0
- package/dist/canary/inventory.d.ts +46 -0
- package/dist/canary/inventory.js +499 -0
- package/dist/canary/inventory.js.map +1 -0
- package/dist/canary/pacifica.d.ts +51 -0
- package/dist/canary/pacifica.js +247 -0
- package/dist/canary/pacifica.js.map +1 -0
- package/dist/canary/plan.d.ts +115 -0
- package/dist/canary/plan.js +479 -0
- package/dist/canary/plan.js.map +1 -0
- package/dist/character/addonPushOut.d.ts +66 -0
- package/dist/character/addonPushOut.js +211 -0
- package/dist/character/addonPushOut.js.map +1 -0
- package/dist/character/avatarPreflight.d.ts +92 -0
- package/dist/character/avatarPreflight.js +261 -0
- package/dist/character/avatarPreflight.js.map +1 -0
- package/dist/character/bakeBodyRegions.d.ts +80 -0
- package/dist/character/bakeBodyRegions.js +190 -0
- package/dist/character/bakeBodyRegions.js.map +1 -0
- package/dist/character/baseColorGuard.d.ts +62 -0
- package/dist/character/baseColorGuard.js +141 -0
- package/dist/character/baseColorGuard.js.map +1 -0
- package/dist/character/bodyGeometryGuard.d.ts +243 -0
- package/dist/character/bodyGeometryGuard.js +417 -0
- package/dist/character/bodyGeometryGuard.js.map +1 -0
- package/dist/character/bodyRegionHide.d.ts +57 -0
- package/dist/character/bodyRegionHide.js +97 -0
- package/dist/character/bodyRegionHide.js.map +1 -0
- package/dist/character/composeBaseMale.d.ts +25 -0
- package/dist/character/composeBaseMale.js +712 -0
- package/dist/character/composeBaseMale.js.map +1 -0
- package/dist/character/esm.js +3 -1
- package/dist/character/esm.js.map +1 -1
- package/dist/character/faceManifest.d.ts +53 -0
- package/dist/character/faceManifest.js +256 -0
- package/dist/character/faceManifest.js.map +1 -0
- package/dist/character/fbx.d.ts +70 -0
- package/dist/character/fbx.js +396 -0
- package/dist/character/fbx.js.map +1 -0
- package/dist/character/fitGarmentFile.d.ts +39 -0
- package/dist/character/fitGarmentFile.js +53 -0
- package/dist/character/fitGarmentFile.js.map +1 -0
- package/dist/character/helixPalette.d.ts +68 -0
- package/dist/character/helixPalette.js +183 -0
- package/dist/character/helixPalette.js.map +1 -0
- package/dist/character/hideCoverage.d.ts +135 -0
- package/dist/character/hideCoverage.js +450 -0
- package/dist/character/hideCoverage.js.map +1 -0
- package/dist/character/imageLimits.d.ts +3 -0
- package/dist/character/imageLimits.js +7 -0
- package/dist/character/imageLimits.js.map +1 -0
- package/dist/character/importCharacter.d.ts +188 -10
- package/dist/character/importCharacter.js +854 -76
- package/dist/character/importCharacter.js.map +1 -1
- package/dist/character/importTrust.d.ts +11 -0
- package/dist/character/importTrust.js +56 -0
- package/dist/character/importTrust.js.map +1 -0
- package/dist/character/ktx2Decode.d.ts +11 -0
- package/dist/character/ktx2Decode.js +87 -0
- package/dist/character/ktx2Decode.js.map +1 -0
- package/dist/character/mat4.d.ts +7 -0
- package/dist/character/mat4.js +25 -2
- package/dist/character/mat4.js.map +1 -1
- package/dist/character/meshyToHelixMap.d.ts +14 -0
- package/dist/character/meshyToHelixMap.js +155 -3
- package/dist/character/meshyToHelixMap.js.map +1 -1
- package/dist/character/metahumanToHelixMap.d.ts +2 -0
- package/dist/character/metahumanToHelixMap.js +34 -0
- package/dist/character/metahumanToHelixMap.js.map +1 -0
- package/dist/character/normalMapGuard.d.ts +59 -0
- package/dist/character/normalMapGuard.js +139 -0
- package/dist/character/normalMapGuard.js.map +1 -0
- package/dist/character/quat.js +2 -2
- package/dist/character/quat.js.map +1 -1
- package/dist/character/reframeSkeletonFile.d.ts +19 -0
- package/dist/character/reframeSkeletonFile.js +45 -0
- package/dist/character/reframeSkeletonFile.js.map +1 -0
- package/dist/character/repairLodSkeletonsFile.d.ts +25 -0
- package/dist/character/repairLodSkeletonsFile.js +49 -0
- package/dist/character/repairLodSkeletonsFile.js.map +1 -0
- package/dist/character/restFrameGuard.d.ts +52 -0
- package/dist/character/restFrameGuard.js +90 -0
- package/dist/character/restFrameGuard.js.map +1 -0
- package/dist/character/slotBudgets.d.ts +37 -0
- package/dist/character/slotBudgets.generated.d.ts +15 -0
- package/dist/character/slotBudgets.generated.js +110 -0
- package/dist/character/slotBudgets.generated.js.map +1 -0
- package/dist/character/slotBudgets.js +45 -0
- package/dist/character/slotBudgets.js.map +1 -0
- package/dist/character/stages/atlasMaterials.d.ts +53 -0
- package/dist/character/stages/atlasMaterials.js +246 -0
- package/dist/character/stages/atlasMaterials.js.map +1 -0
- package/dist/character/stages/bakeTexture.d.ts +28 -7
- package/dist/character/stages/bakeTexture.js +586 -108
- package/dist/character/stages/bakeTexture.js.map +1 -1
- package/dist/character/stages/conform.d.ts +54 -9
- package/dist/character/stages/conform.js +686 -113
- package/dist/character/stages/conform.js.map +1 -1
- package/dist/character/stages/earClearance.d.ts +105 -0
- package/dist/character/stages/earClearance.js +480 -0
- package/dist/character/stages/earClearance.js.map +1 -0
- package/dist/character/stages/faceRegion.d.ts +55 -0
- package/dist/character/stages/faceRegion.js +183 -0
- package/dist/character/stages/faceRegion.js.map +1 -0
- package/dist/character/stages/firstPersonHide.d.ts +42 -0
- package/dist/character/stages/firstPersonHide.js +140 -0
- package/dist/character/stages/firstPersonHide.js.map +1 -0
- package/dist/character/stages/joinPerMesh.d.ts +18 -0
- package/dist/character/stages/joinPerMesh.js +46 -0
- package/dist/character/stages/joinPerMesh.js.map +1 -0
- package/dist/character/stages/ktx2.d.ts +6 -4
- package/dist/character/stages/ktx2.js +81 -29
- package/dist/character/stages/ktx2.js.map +1 -1
- package/dist/character/stages/limbBridge.d.ts +114 -0
- package/dist/character/stages/limbBridge.js +1286 -0
- package/dist/character/stages/limbBridge.js.map +1 -0
- package/dist/character/stages/mergeLods.d.ts +19 -0
- package/dist/character/stages/mergeLods.js +58 -2
- package/dist/character/stages/mergeLods.js.map +1 -1
- package/dist/character/stages/poseAwareFit.d.ts +192 -0
- package/dist/character/stages/poseAwareFit.js +417 -0
- package/dist/character/stages/poseAwareFit.js.map +1 -0
- package/dist/character/stages/poseFit/math.d.ts +141 -0
- package/dist/character/stages/poseFit/math.js +514 -0
- package/dist/character/stages/poseFit/math.js.map +1 -0
- package/dist/character/stages/poseFit/measure.d.ts +88 -0
- package/dist/character/stages/poseFit/measure.js +148 -0
- package/dist/character/stages/poseFit/measure.js.map +1 -0
- package/dist/character/stages/poseFit/model.d.ts +46 -0
- package/dist/character/stages/poseFit/model.js +147 -0
- package/dist/character/stages/poseFit/model.js.map +1 -0
- package/dist/character/stages/poseFit/offsetField.d.ts +76 -0
- package/dist/character/stages/poseFit/offsetField.js +249 -0
- package/dist/character/stages/poseFit/offsetField.js.map +1 -0
- package/dist/character/stages/poseFit/posing.d.ts +52 -0
- package/dist/character/stages/poseFit/posing.js +330 -0
- package/dist/character/stages/poseFit/posing.js.map +1 -0
- package/dist/character/stages/poseFit/testRig.d.ts +31 -0
- package/dist/character/stages/poseFit/testRig.js +85 -0
- package/dist/character/stages/poseFit/testRig.js.map +1 -0
- package/dist/character/stages/reframeSkeletonToHost.d.ts +35 -0
- package/dist/character/stages/reframeSkeletonToHost.js +111 -0
- package/dist/character/stages/reframeSkeletonToHost.js.map +1 -0
- package/dist/character/stages/report.js +0 -2
- package/dist/character/stages/report.js.map +1 -1
- package/dist/character/stages/resizeTextures.js +4 -4
- package/dist/character/stages/resizeTextures.js.map +1 -1
- package/dist/character/stages/retarget.d.ts +26 -2
- package/dist/character/stages/retarget.js +52 -4
- package/dist/character/stages/retarget.js.map +1 -1
- package/dist/character/stages/retargetClip.d.ts +6 -2
- package/dist/character/stages/retargetClip.js +109 -17
- package/dist/character/stages/retargetClip.js.map +1 -1
- package/dist/character/stages/sanitizeMaterials.d.ts +39 -0
- package/dist/character/stages/sanitizeMaterials.js +136 -0
- package/dist/character/stages/sanitizeMaterials.js.map +1 -0
- package/dist/character/stages/simplifyMesh.js +6 -3
- package/dist/character/stages/simplifyMesh.js.map +1 -1
- package/dist/character/stages/splitFacePrimitive.js +0 -2
- package/dist/character/stages/splitFacePrimitive.js.map +1 -1
- package/dist/character/stages/splitSharedLodSkeletons.d.ts +31 -0
- package/dist/character/stages/splitSharedLodSkeletons.js +104 -0
- package/dist/character/stages/splitSharedLodSkeletons.js.map +1 -0
- package/dist/character/stages/unifyMaterial.d.ts +66 -0
- package/dist/character/stages/unifyMaterial.js +136 -0
- package/dist/character/stages/unifyMaterial.js.map +1 -0
- package/dist/character/stages/vrmDynamics.d.ts +144 -0
- package/dist/character/stages/vrmDynamics.js +421 -0
- package/dist/character/stages/vrmDynamics.js.map +1 -0
- package/dist/character/stages/vrmFace.d.ts +82 -0
- package/dist/character/stages/vrmFace.js +280 -0
- package/dist/character/stages/vrmFace.js.map +1 -0
- package/dist/character/stages/weldedSimplify.d.ts +15 -1
- package/dist/character/stages/weldedSimplify.js +90 -9
- package/dist/character/stages/weldedSimplify.js.map +1 -1
- package/dist/character/testutil/meshyBridgeFixture.d.ts +33 -0
- package/dist/character/testutil/meshyBridgeFixture.js +219 -0
- package/dist/character/testutil/meshyBridgeFixture.js.map +1 -0
- package/dist/character/testutil/meshyHead.d.ts +19 -0
- package/dist/character/testutil/meshyHead.js +99 -0
- package/dist/character/testutil/meshyHead.js.map +1 -0
- package/dist/character/testutil/meshyRig.d.ts +17 -0
- package/dist/character/testutil/meshyRig.js +117 -0
- package/dist/character/testutil/meshyRig.js.map +1 -0
- package/dist/character/testutil/slotBudgetExpectations.d.ts +11 -0
- package/dist/character/testutil/slotBudgetExpectations.js +21 -0
- package/dist/character/testutil/slotBudgetExpectations.js.map +1 -0
- package/dist/character/testutil/syntheticRig.d.ts +12 -1
- package/dist/character/testutil/syntheticRig.js +88 -3
- package/dist/character/testutil/syntheticRig.js.map +1 -1
- package/dist/character/unrealMaterialSidecar.d.ts +94 -0
- package/dist/character/unrealMaterialSidecar.js +879 -0
- package/dist/character/unrealMaterialSidecar.js.map +1 -0
- package/dist/character/untrustedSource.d.ts +54 -0
- package/dist/character/untrustedSource.js +181 -0
- package/dist/character/untrustedSource.js.map +1 -0
- package/dist/character/vrmCharacter.d.ts +156 -0
- package/dist/character/vrmCharacter.js +678 -0
- package/dist/character/vrmCharacter.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +72 -24
- package/dist/config.js.map +1 -1
- package/dist/continuum/adminPackagePinCommand.d.ts +14 -0
- package/dist/continuum/adminPackagePinCommand.js +46 -0
- package/dist/continuum/adminPackagePinCommand.js.map +1 -0
- package/dist/continuum/adminPackagePublishCommand.d.ts +13 -0
- package/dist/continuum/adminPackagePublishCommand.js +22 -0
- package/dist/continuum/adminPackagePublishCommand.js.map +1 -0
- package/dist/continuum/authoring.d.ts +52 -0
- package/dist/continuum/authoring.js +143 -0
- package/dist/continuum/authoring.js.map +1 -0
- package/dist/continuum/characterMigrationGenerator.d.ts +17 -0
- package/dist/continuum/characterMigrationGenerator.js +242 -0
- package/dist/continuum/characterMigrationGenerator.js.map +1 -0
- package/dist/continuum/cli.d.ts +69 -0
- package/dist/continuum/cli.js +145 -0
- package/dist/continuum/cli.js.map +1 -0
- package/dist/continuum/cliAuthority.d.ts +17 -0
- package/dist/continuum/cliAuthority.js +28 -0
- package/dist/continuum/cliAuthority.js.map +1 -0
- package/dist/continuum/client.d.ts +103 -0
- package/dist/continuum/client.js +190 -0
- package/dist/continuum/client.js.map +1 -0
- package/dist/continuum/homeScenePackage.d.ts +159 -0
- package/dist/continuum/homeScenePackage.js +267 -0
- package/dist/continuum/homeScenePackage.js.map +1 -0
- package/dist/continuum/homeScenePackageCommand.d.ts +44 -0
- package/dist/continuum/homeScenePackageCommand.js +107 -0
- package/dist/continuum/homeScenePackageCommand.js.map +1 -0
- package/dist/continuum/identity.d.ts +4 -0
- package/dist/continuum/identity.js +20 -0
- package/dist/continuum/identity.js.map +1 -0
- package/dist/continuum/index.d.ts +11 -0
- package/dist/continuum/index.js +28 -0
- package/dist/continuum/index.js.map +1 -0
- package/dist/continuum/missingNormals.d.ts +48 -0
- package/dist/continuum/missingNormals.js +99 -0
- package/dist/continuum/missingNormals.js.map +1 -0
- package/dist/continuum/objectUpload.d.ts +64 -0
- package/dist/continuum/objectUpload.js +375 -0
- package/dist/continuum/objectUpload.js.map +1 -0
- package/dist/continuum/pull.d.ts +22 -0
- package/dist/continuum/pull.js +47 -0
- package/dist/continuum/pull.js.map +1 -0
- package/dist/continuum/sourceBundle.d.ts +18 -0
- package/dist/continuum/sourceBundle.js +160 -0
- package/dist/continuum/sourceBundle.js.map +1 -0
- package/dist/continuum/state.d.ts +41 -0
- package/dist/continuum/state.js +198 -0
- package/dist/continuum/state.js.map +1 -0
- package/dist/continuum/types.d.ts +156 -0
- package/dist/continuum/types.js +6 -0
- package/dist/continuum/types.js.map +1 -0
- package/dist/continuum/workflow.d.ts +62 -0
- package/dist/continuum/workflow.js +592 -0
- package/dist/continuum/workflow.js.map +1 -0
- package/dist/cs2/areas.d.ts +41 -0
- package/dist/cs2/areas.js +82 -0
- package/dist/cs2/areas.js.map +1 -0
- package/dist/cs2/binary.d.ts +10 -0
- package/dist/cs2/binary.js +59 -0
- package/dist/cs2/binary.js.map +1 -0
- package/dist/cs2/cityLegacy.d.ts +29 -0
- package/dist/cs2/cityLegacy.js +48 -0
- package/dist/cs2/cityLegacy.js.map +1 -0
- package/dist/cs2/convert/areas.d.ts +32 -0
- package/dist/cs2/convert/areas.js +96 -0
- package/dist/cs2/convert/areas.js.map +1 -0
- package/dist/cs2/convert/glb.d.ts +48 -0
- package/dist/cs2/convert/glb.js +178 -0
- package/dist/cs2/convert/glb.js.map +1 -0
- package/dist/cs2/convert/markings.d.ts +96 -0
- package/dist/cs2/convert/markings.js +329 -0
- package/dist/cs2/convert/markings.js.map +1 -0
- package/dist/cs2/convert/nodeSeams.d.ts +20 -0
- package/dist/cs2/convert/nodeSeams.js +164 -0
- package/dist/cs2/convert/nodeSeams.js.map +1 -0
- package/dist/cs2/convert/roadBake.d.ts +112 -0
- package/dist/cs2/convert/roadBake.js +290 -0
- package/dist/cs2/convert/roadBake.js.map +1 -0
- package/dist/cs2/convert/roads.d.ts +110 -0
- package/dist/cs2/convert/roads.js +419 -0
- package/dist/cs2/convert/roads.js.map +1 -0
- package/dist/cs2/convert/terrain.d.ts +67 -0
- package/dist/cs2/convert/terrain.js +141 -0
- package/dist/cs2/convert/terrain.js.map +1 -0
- package/dist/cs2/convert/terrainConform.d.ts +58 -0
- package/dist/cs2/convert/terrainConform.js +284 -0
- package/dist/cs2/convert/terrainConform.js.map +1 -0
- package/dist/cs2/convert/treeRoadClearance.d.ts +17 -0
- package/dist/cs2/convert/treeRoadClearance.js +78 -0
- package/dist/cs2/convert/treeRoadClearance.js.map +1 -0
- package/dist/cs2/convert/trees.d.ts +33 -0
- package/dist/cs2/convert/trees.js +68 -0
- package/dist/cs2/convert/trees.js.map +1 -0
- package/dist/cs2/csm2.d.ts +58 -0
- package/dist/cs2/csm2.js +188 -0
- package/dist/cs2/csm2.js.map +1 -0
- package/dist/cs2/fixtures.d.ts +5 -0
- package/dist/cs2/fixtures.js +15 -0
- package/dist/cs2/fixtures.js.map +1 -0
- package/dist/cs2/impostors.d.ts +31 -0
- package/dist/cs2/impostors.js +42 -0
- package/dist/cs2/impostors.js.map +1 -0
- package/dist/cs2/index.d.ts +22 -0
- package/dist/cs2/index.js +191 -0
- package/dist/cs2/index.js.map +1 -0
- package/dist/cs2/inspect.d.ts +81 -0
- package/dist/cs2/inspect.js +181 -0
- package/dist/cs2/inspect.js.map +1 -0
- package/dist/cs2/lods.d.ts +22 -0
- package/dist/cs2/lods.js +136 -0
- package/dist/cs2/lods.js.map +1 -0
- package/dist/cs2/materials.d.ts +43 -0
- package/dist/cs2/materials.js +137 -0
- package/dist/cs2/materials.js.map +1 -0
- package/dist/cs2/placements.d.ts +57 -0
- package/dist/cs2/placements.js +116 -0
- package/dist/cs2/placements.js.map +1 -0
- package/dist/cs2/roadPieces.d.ts +79 -0
- package/dist/cs2/roadPieces.js +263 -0
- package/dist/cs2/roadPieces.js.map +1 -0
- package/dist/cs2/roads.d.ts +92 -0
- package/dist/cs2/roads.js +329 -0
- package/dist/cs2/roads.js.map +1 -0
- package/dist/cs2/terrain.d.ts +37 -0
- package/dist/cs2/terrain.js +91 -0
- package/dist/cs2/terrain.js.map +1 -0
- package/dist/cs2/types.d.ts +444 -0
- package/dist/cs2/types.js +22 -0
- package/dist/cs2/types.js.map +1 -0
- package/dist/cs2/water.d.ts +26 -0
- package/dist/cs2/water.js +58 -0
- package/dist/cs2/water.js.map +1 -0
- package/dist/currency.d.ts +94 -0
- package/dist/currency.js +425 -0
- package/dist/currency.js.map +1 -0
- package/dist/dev/devServer.d.ts +53 -0
- package/dist/dev/devServer.js +228 -0
- package/dist/dev/devServer.js.map +1 -0
- package/dist/dev/shellPage.d.ts +3 -0
- package/dist/dev/shellPage.js +297 -0
- package/dist/dev/shellPage.js.map +1 -0
- package/dist/dev/state.d.ts +84 -0
- package/dist/dev/state.js +265 -0
- package/dist/dev/state.js.map +1 -0
- package/dist/device.d.ts +104 -0
- package/dist/device.js +2744 -0
- package/dist/device.js.map +1 -0
- package/dist/deviceActionValidation.d.ts +21 -0
- package/dist/deviceActionValidation.js +460 -0
- package/dist/deviceActionValidation.js.map +1 -0
- package/dist/distribution.d.ts +69 -0
- package/dist/distribution.js +138 -0
- package/dist/distribution.js.map +1 -0
- package/dist/enginePins.d.ts +45 -0
- package/dist/enginePins.js +134 -0
- package/dist/enginePins.js.map +1 -0
- package/dist/gesture/axisReference.d.ts +22 -0
- package/dist/gesture/axisReference.js +32 -0
- package/dist/gesture/axisReference.js.map +1 -0
- package/dist/gesture/capture.d.ts +84 -0
- package/dist/gesture/capture.js +303 -0
- package/dist/gesture/capture.js.map +1 -0
- package/dist/gesture/harnessPage.d.ts +23 -0
- package/dist/gesture/harnessPage.js +153 -0
- package/dist/gesture/harnessPage.js.map +1 -0
- package/dist/gesture/inspect.d.ts +189 -0
- package/dist/gesture/inspect.js +678 -0
- package/dist/gesture/inspect.js.map +1 -0
- package/dist/gesture/rig.d.ts +12 -0
- package/dist/gesture/rig.js +111 -0
- package/dist/gesture/rig.js.map +1 -0
- package/dist/gesture/worldRuntime.d.ts +65 -0
- package/dist/gesture/worldRuntime.js +110 -0
- package/dist/gesture/worldRuntime.js.map +1 -0
- package/dist/glb/draw-calls.d.ts +60 -0
- package/dist/glb/draw-calls.js +160 -0
- package/dist/glb/draw-calls.js.map +1 -0
- package/dist/glb/glb-summary.d.ts +68 -0
- package/dist/glb/glb-summary.js +450 -0
- package/dist/glb/glb-summary.js.map +1 -0
- package/dist/glb/index.d.ts +12 -0
- package/dist/glb/index.js +24 -0
- package/dist/glb/index.js.map +1 -0
- package/dist/glb/smooth-normals.d.ts +98 -0
- package/dist/glb/smooth-normals.js +395 -0
- package/dist/glb/smooth-normals.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6786 -128
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +35 -1
- package/dist/init.js +1115 -47
- package/dist/init.js.map +1 -1
- package/dist/install.d.ts +16 -5
- package/dist/install.js +123 -23
- package/dist/install.js.map +1 -1
- package/dist/interactable.d.ts +154 -0
- package/dist/interactable.js +1219 -0
- package/dist/interactable.js.map +1 -0
- package/dist/item.d.ts +217 -0
- package/dist/item.js +892 -0
- package/dist/item.js.map +1 -0
- package/dist/itemDef/distribute.d.ts +17 -0
- package/dist/itemDef/distribute.js +29 -0
- package/dist/itemDef/distribute.js.map +1 -0
- package/dist/itemDef/emoteRules.d.ts +85 -0
- package/dist/itemDef/emoteRules.js +368 -0
- package/dist/itemDef/emoteRules.js.map +1 -0
- package/dist/itemDef/gunConfigRules.d.ts +24 -0
- package/dist/itemDef/gunConfigRules.js +300 -0
- package/dist/itemDef/gunConfigRules.js.map +1 -0
- package/dist/itemDef/inspect.d.ts +19 -0
- package/dist/itemDef/inspect.js +144 -0
- package/dist/itemDef/inspect.js.map +1 -0
- package/dist/itemDef/scaffold.d.ts +41 -0
- package/dist/itemDef/scaffold.js +303 -0
- package/dist/itemDef/scaffold.js.map +1 -0
- package/dist/itemDef/tables.d.ts +81 -0
- package/dist/itemDef/tables.js +226 -0
- package/dist/itemDef/tables.js.map +1 -0
- package/dist/itemDef/validate.d.ts +30 -0
- package/dist/itemDef/validate.js +175 -0
- package/dist/itemDef/validate.js.map +1 -0
- package/dist/itemPublishVersion.d.ts +46 -0
- package/dist/itemPublishVersion.js +242 -0
- package/dist/itemPublishVersion.js.map +1 -0
- package/dist/itemSearch.d.ts +128 -0
- package/dist/itemSearch.js +140 -0
- package/dist/itemSearch.js.map +1 -0
- package/dist/itemSetVersion.d.ts +77 -0
- package/dist/itemSetVersion.js +170 -0
- package/dist/itemSetVersion.js.map +1 -0
- package/dist/itemThumbnail.d.ts +40 -0
- package/dist/itemThumbnail.js +116 -0
- package/dist/itemThumbnail.js.map +1 -0
- package/dist/lib.d.ts +571 -7
- package/dist/lib.js +1178 -15
- package/dist/lib.js.map +1 -1
- package/dist/looks.d.ts +103 -0
- package/dist/looks.js +102 -0
- package/dist/looks.js.map +1 -0
- package/dist/materials/availability.d.ts +8 -0
- package/dist/materials/availability.js +22 -0
- package/dist/materials/availability.js.map +1 -0
- package/dist/materials/identity.d.ts +30 -0
- package/dist/materials/identity.js +68 -0
- package/dist/materials/identity.js.map +1 -0
- package/dist/materials/pins.d.ts +23 -0
- package/dist/materials/pins.js +93 -0
- package/dist/materials/pins.js.map +1 -0
- package/dist/mesh/surfaceBvh.d.ts +13 -0
- package/dist/mesh/surfaceBvh.js +197 -0
- package/dist/mesh/surfaceBvh.js.map +1 -0
- package/dist/module-validation.d.ts +2 -0
- package/dist/module-validation.js +24 -0
- package/dist/module-validation.js.map +1 -0
- package/dist/money.d.ts +183 -0
- package/dist/money.js +299 -0
- package/dist/money.js.map +1 -0
- package/dist/npc/capture.d.ts +41 -0
- package/dist/npc/capture.js +99 -0
- package/dist/npc/capture.js.map +1 -0
- package/dist/npc/format.d.ts +2 -0
- package/dist/npc/format.js +102 -0
- package/dist/npc/format.js.map +1 -0
- package/dist/npc/inspect.d.ts +23 -0
- package/dist/npc/inspect.js +54 -0
- package/dist/npc/inspect.js.map +1 -0
- package/dist/npc/metrics.d.ts +132 -0
- package/dist/npc/metrics.js +214 -0
- package/dist/npc/metrics.js.map +1 -0
- package/dist/npc/session.d.ts +35 -0
- package/dist/npc/session.js +172 -0
- package/dist/npc/session.js.map +1 -0
- package/dist/pack/locomotionReference.d.ts +40 -0
- package/dist/pack/locomotionReference.js +132 -0
- package/dist/pack/locomotionReference.js.map +1 -0
- package/dist/pack/scaffold.d.ts +15 -0
- package/dist/pack/scaffold.js +144 -0
- package/dist/pack/scaffold.js.map +1 -0
- package/dist/pack/validate.d.ts +67 -0
- package/dist/pack/validate.js +317 -0
- package/dist/pack/validate.js.map +1 -0
- package/dist/package.d.ts +14 -1
- package/dist/package.js +106 -7
- package/dist/package.js.map +1 -1
- package/dist/paletteShowcase.d.ts +183 -0
- package/dist/paletteShowcase.js +454 -0
- package/dist/paletteShowcase.js.map +1 -0
- package/dist/paletteShowcaseProfiles.d.ts +112 -0
- package/dist/paletteShowcaseProfiles.js +121 -0
- package/dist/paletteShowcaseProfiles.js.map +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +11 -0
- package/dist/paths.js.map +1 -0
- package/dist/placementRequest.d.ts +37 -0
- package/dist/placementRequest.js +220 -0
- package/dist/placementRequest.js.map +1 -0
- package/dist/placements/assets.d.ts +7 -0
- package/dist/placements/assets.js +114 -0
- package/dist/placements/assets.js.map +1 -0
- package/dist/placements/attachFoliageLods.d.ts +4 -0
- package/dist/placements/attachFoliageLods.js +70 -0
- package/dist/placements/attachFoliageLods.js.map +1 -0
- package/dist/placements/attachImpostors.d.ts +3 -0
- package/dist/placements/attachImpostors.js +51 -0
- package/dist/placements/attachImpostors.js.map +1 -0
- package/dist/placements/authored.d.ts +11 -0
- package/dist/placements/authored.js +65 -0
- package/dist/placements/authored.js.map +1 -0
- package/dist/placements/bake.d.ts +2 -0
- package/dist/placements/bake.js +235 -0
- package/dist/placements/bake.js.map +1 -0
- package/dist/placements/captureHost.d.ts +4 -0
- package/dist/placements/captureHost.js +125 -0
- package/dist/placements/captureHost.js.map +1 -0
- package/dist/placements/foliageCapture.d.ts +2 -0
- package/dist/placements/foliageCapture.js +98 -0
- package/dist/placements/foliageCapture.js.map +1 -0
- package/dist/placements/foliageCards.d.ts +17 -0
- package/dist/placements/foliageCards.js +120 -0
- package/dist/placements/foliageCards.js.map +1 -0
- package/dist/placements/foliageLods.d.ts +8 -0
- package/dist/placements/foliageLods.js +240 -0
- package/dist/placements/foliageLods.js.map +1 -0
- package/dist/placements/foliageTexture.d.ts +4 -0
- package/dist/placements/foliageTexture.js +91 -0
- package/dist/placements/foliageTexture.js.map +1 -0
- package/dist/placements/impostorCapture.d.ts +2 -0
- package/dist/placements/impostorCapture.js +111 -0
- package/dist/placements/impostorCapture.js.map +1 -0
- package/dist/placements/impostors.d.ts +18 -0
- package/dist/placements/impostors.js +279 -0
- package/dist/placements/impostors.js.map +1 -0
- package/dist/placements/source.d.ts +53 -0
- package/dist/placements/source.js +111 -0
- package/dist/placements/source.js.map +1 -0
- package/dist/product.d.ts +84 -0
- package/dist/product.js +285 -0
- package/dist/product.js.map +1 -0
- package/dist/publications.d.ts +116 -0
- package/dist/publications.js +180 -0
- package/dist/publications.js.map +1 -0
- package/dist/publishDuplicateGuard.d.ts +92 -0
- package/dist/publishDuplicateGuard.js +128 -0
- package/dist/publishDuplicateGuard.js.map +1 -0
- package/dist/realizability.d.ts +36 -0
- package/dist/realizability.js +269 -0
- package/dist/realizability.js.map +1 -0
- package/dist/resolve.d.ts +7 -1
- package/dist/resolve.js +77 -11
- package/dist/resolve.js.map +1 -1
- package/dist/sceneElements/compiler.d.ts +21 -0
- package/dist/sceneElements/compiler.js +976 -0
- package/dist/sceneElements/compiler.js.map +1 -0
- package/dist/sceneElements/continuum.d.ts +54 -0
- package/dist/sceneElements/continuum.js +451 -0
- package/dist/sceneElements/continuum.js.map +1 -0
- package/dist/sceneElements/devices.d.ts +12 -0
- package/dist/sceneElements/devices.js +212 -0
- package/dist/sceneElements/devices.js.map +1 -0
- package/dist/sceneElements/meshCells.d.ts +49 -0
- package/dist/sceneElements/meshCells.js +174 -0
- package/dist/sceneElements/meshCells.js.map +1 -0
- package/dist/sceneElements/types.d.ts +261 -0
- package/dist/sceneElements/types.js +3 -0
- package/dist/sceneElements/types.js.map +1 -0
- package/dist/sceneHlod/approximate.d.ts +23 -0
- package/dist/sceneHlod/approximate.js +171 -0
- package/dist/sceneHlod/approximate.js.map +1 -0
- package/dist/sceneHlod/artifact.d.ts +3 -0
- package/dist/sceneHlod/artifact.js +29 -0
- package/dist/sceneHlod/artifact.js.map +1 -0
- package/dist/sceneHlod/bakeSurface.d.ts +2 -0
- package/dist/sceneHlod/bakeSurface.js +59 -0
- package/dist/sceneHlod/bakeSurface.js.map +1 -0
- package/dist/sceneHlod/basisEncoder.d.ts +3 -0
- package/dist/sceneHlod/basisEncoder.js +68 -0
- package/dist/sceneHlod/basisEncoder.js.map +1 -0
- package/dist/sceneHlod/cellArtifact.d.ts +17 -0
- package/dist/sceneHlod/cellArtifact.js +78 -0
- package/dist/sceneHlod/cellArtifact.js.map +1 -0
- package/dist/sceneHlod/commands.d.ts +2 -0
- package/dist/sceneHlod/commands.js +114 -0
- package/dist/sceneHlod/commands.js.map +1 -0
- package/dist/sceneHlod/complete.d.ts +78 -0
- package/dist/sceneHlod/complete.js +261 -0
- package/dist/sceneHlod/complete.js.map +1 -0
- package/dist/sceneHlod/completePrototype.d.ts +39 -0
- package/dist/sceneHlod/completePrototype.js +121 -0
- package/dist/sceneHlod/completePrototype.js.map +1 -0
- package/dist/sceneHlod/geometry.d.ts +6 -0
- package/dist/sceneHlod/geometry.js +101 -0
- package/dist/sceneHlod/geometry.js.map +1 -0
- package/dist/sceneHlod/imageSampling.d.ts +11 -0
- package/dist/sceneHlod/imageSampling.js +100 -0
- package/dist/sceneHlod/imageSampling.js.map +1 -0
- package/dist/sceneHlod/refinement.d.ts +46 -0
- package/dist/sceneHlod/refinement.js +204 -0
- package/dist/sceneHlod/refinement.js.map +1 -0
- package/dist/sceneHlod/scene.d.ts +80 -0
- package/dist/sceneHlod/scene.js +264 -0
- package/dist/sceneHlod/scene.js.map +1 -0
- package/dist/sceneHlod/sharedAtlas.d.ts +37 -0
- package/dist/sceneHlod/sharedAtlas.js +84 -0
- package/dist/sceneHlod/sharedAtlas.js.map +1 -0
- package/dist/sceneHlod/source.d.ts +17 -0
- package/dist/sceneHlod/source.js +241 -0
- package/dist/sceneHlod/source.js.map +1 -0
- package/dist/sceneHlod/textureCodec.d.ts +4 -0
- package/dist/sceneHlod/textureCodec.js +66 -0
- package/dist/sceneHlod/textureCodec.js.map +1 -0
- package/dist/sceneHlod/transfer.d.ts +8 -0
- package/dist/sceneHlod/transfer.js +147 -0
- package/dist/sceneHlod/transfer.js.map +1 -0
- package/dist/sceneHlod/types.d.ts +69 -0
- package/dist/sceneHlod/types.js +3 -0
- package/dist/sceneHlod/types.js.map +1 -0
- package/dist/sceneImport/archive.d.ts +30 -0
- package/dist/sceneImport/archive.js +336 -0
- package/dist/sceneImport/archive.js.map +1 -0
- package/dist/sceneImport/bistroMaterialBake.d.ts +20 -0
- package/dist/sceneImport/bistroMaterialBake.js +169 -0
- package/dist/sceneImport/bistroMaterialBake.js.map +1 -0
- package/dist/sceneImport/bistroTexturePlan.d.ts +6 -0
- package/dist/sceneImport/bistroTexturePlan.js +23 -0
- package/dist/sceneImport/bistroTexturePlan.js.map +1 -0
- package/dist/sceneImport/compiler.d.ts +36 -0
- package/dist/sceneImport/compiler.js +251 -0
- package/dist/sceneImport/compiler.js.map +1 -0
- package/dist/sceneImport/composeMounts.d.ts +159 -0
- package/dist/sceneImport/composeMounts.js +687 -0
- package/dist/sceneImport/composeMounts.js.map +1 -0
- package/dist/sceneImport/defaultMaterials.d.ts +45 -0
- package/dist/sceneImport/defaultMaterials.js +121 -0
- package/dist/sceneImport/defaultMaterials.js.map +1 -0
- package/dist/sceneImport/externalTextures.d.ts +44 -0
- package/dist/sceneImport/externalTextures.js +146 -0
- package/dist/sceneImport/externalTextures.js.map +1 -0
- package/dist/sceneImport/fbx.d.ts +101 -0
- package/dist/sceneImport/fbx.js +307 -0
- package/dist/sceneImport/fbx.js.map +1 -0
- package/dist/sceneImport/glbSplit.d.ts +24 -0
- package/dist/sceneImport/glbSplit.js +95 -0
- package/dist/sceneImport/glbSplit.js.map +1 -0
- package/dist/sceneImport/gltfIo.d.ts +32 -0
- package/dist/sceneImport/gltfIo.js +39 -0
- package/dist/sceneImport/gltfIo.js.map +1 -0
- package/dist/sceneImport/helsinkiTiles.d.ts +31 -0
- package/dist/sceneImport/helsinkiTiles.js +52 -0
- package/dist/sceneImport/helsinkiTiles.js.map +1 -0
- package/dist/sceneImport/materials/binding.d.ts +16 -0
- package/dist/sceneImport/materials/binding.js +42 -0
- package/dist/sceneImport/materials/binding.js.map +1 -0
- package/dist/sceneImport/materials/catalog.d.ts +36 -0
- package/dist/sceneImport/materials/catalog.js +233 -0
- package/dist/sceneImport/materials/catalog.js.map +1 -0
- package/dist/sceneImport/materials/index.d.ts +4 -0
- package/dist/sceneImport/materials/index.js +21 -0
- package/dist/sceneImport/materials/index.js.map +1 -0
- package/dist/sceneImport/materials/matcher.d.ts +33 -0
- package/dist/sceneImport/materials/matcher.js +127 -0
- package/dist/sceneImport/materials/matcher.js.map +1 -0
- package/dist/sceneImport/materials/recipe.d.ts +30 -0
- package/dist/sceneImport/materials/recipe.js +134 -0
- package/dist/sceneImport/materials/recipe.js.map +1 -0
- package/dist/sceneImport/multiAsset.d.ts +79 -0
- package/dist/sceneImport/multiAsset.js +535 -0
- package/dist/sceneImport/multiAsset.js.map +1 -0
- package/dist/sceneImport/multiAssetSpawn.d.ts +40 -0
- package/dist/sceneImport/multiAssetSpawn.js +98 -0
- package/dist/sceneImport/multiAssetSpawn.js.map +1 -0
- package/dist/sceneImport/obj.d.ts +41 -0
- package/dist/sceneImport/obj.js +276 -0
- package/dist/sceneImport/obj.js.map +1 -0
- package/dist/sceneImport/package.d.ts +104 -0
- package/dist/sceneImport/package.js +1004 -0
- package/dist/sceneImport/package.js.map +1 -0
- package/dist/sceneImport/packageReader.d.ts +10 -0
- package/dist/sceneImport/packageReader.js +250 -0
- package/dist/sceneImport/packageReader.js.map +1 -0
- package/dist/sceneImport/proxy.d.ts +100 -0
- package/dist/sceneImport/proxy.js +334 -0
- package/dist/sceneImport/proxy.js.map +1 -0
- package/dist/sceneImport/publish.d.ts +47 -0
- package/dist/sceneImport/publish.js +98 -0
- package/dist/sceneImport/publish.js.map +1 -0
- package/dist/sceneImport/semantic.d.ts +75 -0
- package/dist/sceneImport/semantic.js +296 -0
- package/dist/sceneImport/semantic.js.map +1 -0
- package/dist/sceneImport/spawnProbe.d.ts +55 -0
- package/dist/sceneImport/spawnProbe.js +159 -0
- package/dist/sceneImport/spawnProbe.js.map +1 -0
- package/dist/sceneImport/streamLayout.d.ts +96 -0
- package/dist/sceneImport/streamLayout.js +203 -0
- package/dist/sceneImport/streamLayout.js.map +1 -0
- package/dist/sceneImport/textureDecode.d.ts +21 -0
- package/dist/sceneImport/textureDecode.js +292 -0
- package/dist/sceneImport/textureDecode.js.map +1 -0
- package/dist/sceneImport/types.d.ts +84 -0
- package/dist/sceneImport/types.js +58 -0
- package/dist/sceneImport/types.js.map +1 -0
- package/dist/scenePerformance.d.ts +162 -0
- package/dist/scenePerformance.js +905 -0
- package/dist/scenePerformance.js.map +1 -0
- package/dist/screenshot/captureScreenshots.d.ts +36 -0
- package/dist/screenshot/captureScreenshots.js +229 -0
- package/dist/screenshot/captureScreenshots.js.map +1 -0
- package/dist/screenshot/encodeImagePreview.d.ts +5 -0
- package/dist/screenshot/encodeImagePreview.js +42 -0
- package/dist/screenshot/encodeImagePreview.js.map +1 -0
- package/dist/screenshot/publishThumbnail.d.ts +4 -0
- package/dist/screenshot/publishThumbnail.js +56 -0
- package/dist/screenshot/publishThumbnail.js.map +1 -0
- package/dist/screenshot/staticServe.d.ts +14 -0
- package/dist/screenshot/staticServe.js +120 -0
- package/dist/screenshot/staticServe.js.map +1 -0
- package/dist/source-archive.d.ts +101 -0
- package/dist/source-archive.js +309 -0
- package/dist/source-archive.js.map +1 -0
- package/dist/stdioBlocking.d.ts +1 -0
- package/dist/stdioBlocking.js +9 -0
- package/dist/stdioBlocking.js.map +1 -0
- package/dist/terrain/bake.d.ts +40 -0
- package/dist/terrain/bake.js +318 -0
- package/dist/terrain/bake.js.map +1 -0
- package/dist/terrain/generate.d.ts +21 -0
- package/dist/terrain/generate.js +239 -0
- package/dist/terrain/generate.js.map +1 -0
- package/dist/terrain/grid.d.ts +48 -0
- package/dist/terrain/grid.js +133 -0
- package/dist/terrain/grid.js.map +1 -0
- package/dist/terrain/import.d.ts +40 -0
- package/dist/terrain/import.js +252 -0
- package/dist/terrain/import.js.map +1 -0
- package/dist/terrain/index.d.ts +11 -0
- package/dist/terrain/index.js +88 -0
- package/dist/terrain/index.js.map +1 -0
- package/dist/terrain/inspect.d.ts +35 -0
- package/dist/terrain/inspect.js +115 -0
- package/dist/terrain/inspect.js.map +1 -0
- package/dist/terrain/island.d.ts +31 -0
- package/dist/terrain/island.js +128 -0
- package/dist/terrain/island.js.map +1 -0
- package/dist/terrain/noise.d.ts +79 -0
- package/dist/terrain/noise.js +162 -0
- package/dist/terrain/noise.js.map +1 -0
- package/dist/terrain/normals.d.ts +21 -0
- package/dist/terrain/normals.js +55 -0
- package/dist/terrain/normals.js.map +1 -0
- package/dist/terrain/presets/alpine.json +20 -0
- package/dist/terrain/presets/coast.json +20 -0
- package/dist/terrain/presets/desert.json +22 -0
- package/dist/terrain/presets/forest.json +20 -0
- package/dist/terrain/presets/meadow.json +20 -0
- package/dist/terrain/presets/volcanic.json +22 -0
- package/dist/terrain/presets.d.ts +29 -0
- package/dist/terrain/presets.js +128 -0
- package/dist/terrain/presets.js.map +1 -0
- package/dist/terrain/source.d.ts +42 -0
- package/dist/terrain/source.js +241 -0
- package/dist/terrain/source.js.map +1 -0
- package/dist/terrain/tile.d.ts +92 -0
- package/dist/terrain/tile.js +268 -0
- package/dist/terrain/tile.js.map +1 -0
- package/dist/textureAudit.d.ts +23 -0
- package/dist/textureAudit.js +192 -0
- package/dist/textureAudit.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/unreal/avatarMode.d.ts +21 -0
- package/dist/unreal/avatarMode.js +33 -0
- package/dist/unreal/avatarMode.js.map +1 -0
- package/dist/unreal/delivery.d.ts +46 -0
- package/dist/unreal/delivery.js +89 -0
- package/dist/unreal/delivery.js.map +1 -0
- package/dist/unreal/doctor.d.ts +50 -0
- package/dist/unreal/doctor.js +102 -0
- package/dist/unreal/doctor.js.map +1 -0
- package/dist/unreal/package.d.ts +80 -0
- package/dist/unreal/package.js +273 -0
- package/dist/unreal/package.js.map +1 -0
- package/dist/unreal/release.d.ts +41 -0
- package/dist/unreal/release.js +122 -0
- package/dist/unreal/release.js.map +1 -0
- package/dist/unreal.d.ts +30 -0
- package/dist/unreal.js +362 -0
- package/dist/unreal.js.map +1 -0
- package/dist/unrealCandidate.d.ts +213 -0
- package/dist/unrealCandidate.js +765 -0
- package/dist/unrealCandidate.js.map +1 -0
- package/dist/unrealManifest.d.ts +74 -0
- package/dist/unrealManifest.js +354 -0
- package/dist/unrealManifest.js.map +1 -0
- package/dist/unrealPublish.d.ts +111 -0
- package/dist/unrealPublish.js +331 -0
- package/dist/unrealPublish.js.map +1 -0
- package/dist/vault.d.ts +121 -0
- package/dist/vault.js +136 -0
- package/dist/vault.js.map +1 -0
- package/dist/vehicle/addOnDefinition.d.ts +19 -0
- package/dist/vehicle/addOnDefinition.js +21 -0
- package/dist/vehicle/addOnDefinition.js.map +1 -0
- package/dist/vehicle/addon.d.ts +158 -0
- package/dist/vehicle/addon.js +441 -0
- package/dist/vehicle/addon.js.map +1 -0
- package/dist/vehicle/addonDefinitionGuard.d.ts +53 -0
- package/dist/vehicle/addonDefinitionGuard.js +206 -0
- package/dist/vehicle/addonDefinitionGuard.js.map +1 -0
- package/dist/vehicle/addonPublished.d.ts +80 -0
- package/dist/vehicle/addonPublished.js +221 -0
- package/dist/vehicle/addonPublished.js.map +1 -0
- package/dist/vehicle/audio.d.ts +259 -0
- package/dist/vehicle/audio.js +1069 -0
- package/dist/vehicle/audio.js.map +1 -0
- package/dist/vehicle/audioDefects.d.ts +125 -0
- package/dist/vehicle/audioDefects.js +591 -0
- package/dist/vehicle/audioDefects.js.map +1 -0
- package/dist/vehicle/audioDistinct.d.ts +62 -0
- package/dist/vehicle/audioDistinct.js +479 -0
- package/dist/vehicle/audioDistinct.js.map +1 -0
- package/dist/vehicle/audioImport.d.ts +217 -0
- package/dist/vehicle/audioImport.js +610 -0
- package/dist/vehicle/audioImport.js.map +1 -0
- package/dist/vehicle/audioProvenance.d.ts +47 -0
- package/dist/vehicle/audioProvenance.js +364 -0
- package/dist/vehicle/audioProvenance.js.map +1 -0
- package/dist/vehicle/audioReference.d.ts +36 -0
- package/dist/vehicle/audioReference.js +176 -0
- package/dist/vehicle/audioReference.js.map +1 -0
- package/dist/vehicle/audioSlots.d.ts +27 -0
- package/dist/vehicle/audioSlots.js +43 -0
- package/dist/vehicle/audioSlots.js.map +1 -0
- package/dist/vehicle/audioSpectral.d.ts +71 -0
- package/dist/vehicle/audioSpectral.js +534 -0
- package/dist/vehicle/audioSpectral.js.map +1 -0
- package/dist/vehicle/cabinCheck.d.ts +209 -0
- package/dist/vehicle/cabinCheck.js +799 -0
- package/dist/vehicle/cabinCheck.js.map +1 -0
- package/dist/vehicle/drive.d.ts +66 -0
- package/dist/vehicle/drive.js +228 -0
- package/dist/vehicle/drive.js.map +1 -0
- package/dist/vehicle/enginePolicy.d.ts +68 -0
- package/dist/vehicle/enginePolicy.js +209 -0
- package/dist/vehicle/enginePolicy.js.map +1 -0
- package/dist/vehicle/hostFrame.d.ts +78 -0
- package/dist/vehicle/hostFrame.js +136 -0
- package/dist/vehicle/hostFrame.js.map +1 -0
- package/dist/vehicle/inspectionSheets.d.ts +42 -0
- package/dist/vehicle/inspectionSheets.js +173 -0
- package/dist/vehicle/inspectionSheets.js.map +1 -0
- package/dist/vehicle/keyboardLaunch.d.ts +189 -0
- package/dist/vehicle/keyboardLaunch.js +340 -0
- package/dist/vehicle/keyboardLaunch.js.map +1 -0
- package/dist/vehicle/lampCoverage.d.ts +49 -0
- package/dist/vehicle/lampCoverage.js +252 -0
- package/dist/vehicle/lampCoverage.js.map +1 -0
- package/dist/vehicle/mintAddOn.d.ts +110 -0
- package/dist/vehicle/mintAddOn.js +321 -0
- package/dist/vehicle/mintAddOn.js.map +1 -0
- package/dist/vehicle/mintVehicle.d.ts +189 -0
- package/dist/vehicle/mintVehicle.js +417 -0
- package/dist/vehicle/mintVehicle.js.map +1 -0
- package/dist/vehicle/paintHue.d.ts +89 -0
- package/dist/vehicle/paintHue.js +248 -0
- package/dist/vehicle/paintHue.js.map +1 -0
- package/dist/vehicle/panelCoverage.d.ts +37 -0
- package/dist/vehicle/panelCoverage.js +93 -0
- package/dist/vehicle/panelCoverage.js.map +1 -0
- package/dist/vehicle/platform.d.ts +71 -0
- package/dist/vehicle/platform.js +144 -0
- package/dist/vehicle/platform.js.map +1 -0
- package/dist/vehicle/preview.d.ts +339 -0
- package/dist/vehicle/preview.js +854 -0
- package/dist/vehicle/preview.js.map +1 -0
- package/dist/vehicle/previewAddon.d.ts +197 -0
- package/dist/vehicle/previewAddon.js +520 -0
- package/dist/vehicle/previewAddon.js.map +1 -0
- package/dist/vehicle/previewSeats.d.ts +102 -0
- package/dist/vehicle/previewSeats.js +190 -0
- package/dist/vehicle/previewSeats.js.map +1 -0
- package/dist/vehicle/publish.d.ts +78 -0
- package/dist/vehicle/publish.js +111 -0
- package/dist/vehicle/publish.js.map +1 -0
- package/dist/vehicle/recheckListed.d.ts +87 -0
- package/dist/vehicle/recheckListed.js +200 -0
- package/dist/vehicle/recheckListed.js.map +1 -0
- package/dist/vehicle/renditionManifest.d.ts +10 -0
- package/dist/vehicle/renditionManifest.js +56 -0
- package/dist/vehicle/renditionManifest.js.map +1 -0
- package/dist/vehicle/sealCheck.d.ts +30 -0
- package/dist/vehicle/sealCheck.js +59 -0
- package/dist/vehicle/sealCheck.js.map +1 -0
- package/dist/vehicle/simulate.d.ts +219 -0
- package/dist/vehicle/simulate.js +536 -0
- package/dist/vehicle/simulate.js.map +1 -0
- package/dist/vehicle/simulateRuntime.d.ts +76 -0
- package/dist/vehicle/simulateRuntime.js +252 -0
- package/dist/vehicle/simulateRuntime.js.map +1 -0
- package/dist/vehicle/testutil/sourcedAudio.d.ts +13 -0
- package/dist/vehicle/testutil/sourcedAudio.js +96 -0
- package/dist/vehicle/testutil/sourcedAudio.js.map +1 -0
- package/dist/vehicle/validate.d.ts +225 -0
- package/dist/vehicle/validate.js +956 -0
- package/dist/vehicle/validate.js.map +1 -0
- package/dist/vehicle/wheelDesign.d.ts +98 -0
- package/dist/vehicle/wheelDesign.js +276 -0
- package/dist/vehicle/wheelDesign.js.map +1 -0
- package/dist/verifySubPath.d.ts +69 -0
- package/dist/verifySubPath.js +458 -0
- package/dist/verifySubPath.js.map +1 -0
- package/dist/visualQa/animation.d.ts +57 -0
- package/dist/visualQa/animation.js +503 -0
- package/dist/visualQa/animation.js.map +1 -0
- package/dist/visualQa/avatar.d.ts +3 -0
- package/dist/visualQa/avatar.js +443 -0
- package/dist/visualQa/avatar.js.map +1 -0
- package/dist/visualQa/avatarMath.d.ts +180 -0
- package/dist/visualQa/avatarMath.js +318 -0
- package/dist/visualQa/avatarMath.js.map +1 -0
- package/dist/visualQa/avatarPreview.d.ts +26 -0
- package/dist/visualQa/avatarPreview.js +167 -0
- package/dist/visualQa/avatarPreview.js.map +1 -0
- package/dist/visualQa/avatarPreviewMath.d.ts +189 -0
- package/dist/visualQa/avatarPreviewMath.js +696 -0
- package/dist/visualQa/avatarPreviewMath.js.map +1 -0
- package/dist/visualQa/avatarPreviewProbe.d.ts +7 -0
- package/dist/visualQa/avatarPreviewProbe.js +181 -0
- package/dist/visualQa/avatarPreviewProbe.js.map +1 -0
- package/dist/visualQa/avatarProbe.d.ts +1 -0
- package/dist/visualQa/avatarProbe.js +264 -0
- package/dist/visualQa/avatarProbe.js.map +1 -0
- package/dist/visualQa/browser.d.ts +116 -0
- package/dist/visualQa/browser.js +817 -0
- package/dist/visualQa/browser.js.map +1 -0
- package/dist/visualQa/candidate.d.ts +116 -0
- package/dist/visualQa/candidate.js +265 -0
- package/dist/visualQa/candidate.js.map +1 -0
- package/dist/visualQa/checklists.d.ts +31 -0
- package/dist/visualQa/checklists.js +160 -0
- package/dist/visualQa/checklists.js.map +1 -0
- package/dist/visualQa/command.d.ts +110 -0
- package/dist/visualQa/command.js +521 -0
- package/dist/visualQa/command.js.map +1 -0
- package/dist/visualQa/commonAsserts.d.ts +68 -0
- package/dist/visualQa/commonAsserts.js +239 -0
- package/dist/visualQa/commonAsserts.js.map +1 -0
- package/dist/visualQa/contactSheet.d.ts +32 -0
- package/dist/visualQa/contactSheet.js +153 -0
- package/dist/visualQa/contactSheet.js.map +1 -0
- package/dist/visualQa/critic.d.ts +18 -0
- package/dist/visualQa/critic.js +179 -0
- package/dist/visualQa/critic.js.map +1 -0
- package/dist/visualQa/emotePage.d.ts +10 -0
- package/dist/visualQa/emotePage.js +249 -0
- package/dist/visualQa/emotePage.js.map +1 -0
- package/dist/visualQa/evidenceOverlay.d.ts +74 -0
- package/dist/visualQa/evidenceOverlay.js +268 -0
- package/dist/visualQa/evidenceOverlay.js.map +1 -0
- package/dist/visualQa/harness.d.ts +57 -0
- package/dist/visualQa/harness.js +3 -0
- package/dist/visualQa/harness.js.map +1 -0
- package/dist/visualQa/hosted.d.ts +152 -0
- package/dist/visualQa/hosted.js +367 -0
- package/dist/visualQa/hosted.js.map +1 -0
- package/dist/visualQa/labelLimit.d.ts +12 -0
- package/dist/visualQa/labelLimit.js +31 -0
- package/dist/visualQa/labelLimit.js.map +1 -0
- package/dist/visualQa/mirror.d.ts +36 -0
- package/dist/visualQa/mirror.js +98 -0
- package/dist/visualQa/mirror.js.map +1 -0
- package/dist/visualQa/moveUx.d.ts +44 -0
- package/dist/visualQa/moveUx.js +103 -0
- package/dist/visualQa/moveUx.js.map +1 -0
- package/dist/visualQa/pinnedChrome.d.ts +63 -0
- package/dist/visualQa/pinnedChrome.js +214 -0
- package/dist/visualQa/pinnedChrome.js.map +1 -0
- package/dist/visualQa/resolve.d.ts +88 -0
- package/dist/visualQa/resolve.js +248 -0
- package/dist/visualQa/resolve.js.map +1 -0
- package/dist/visualQa/scene.d.ts +14 -0
- package/dist/visualQa/scene.js +688 -0
- package/dist/visualQa/scene.js.map +1 -0
- package/dist/visualQa/shot.d.ts +2 -0
- package/dist/visualQa/shot.js +27 -0
- package/dist/visualQa/shot.js.map +1 -0
- package/dist/visualQa/signIn.d.ts +35 -0
- package/dist/visualQa/signIn.js +144 -0
- package/dist/visualQa/signIn.js.map +1 -0
- package/dist/visualQa/submitBudget.d.ts +25 -0
- package/dist/visualQa/submitBudget.js +82 -0
- package/dist/visualQa/submitBudget.js.map +1 -0
- package/dist/visualQa/types.d.ts +195 -0
- package/dist/visualQa/types.js +12 -0
- package/dist/visualQa/types.js.map +1 -0
- package/dist/visualQa/vehicle.d.ts +130 -0
- package/dist/visualQa/vehicle.js +2002 -0
- package/dist/visualQa/vehicle.js.map +1 -0
- package/dist/visualQa/vehicleAddonProbe.d.ts +1 -0
- package/dist/visualQa/vehicleAddonProbe.js +115 -0
- package/dist/visualQa/vehicleAddonProbe.js.map +1 -0
- package/dist/visualQa/vehicleCabinFit.d.ts +78 -0
- package/dist/visualQa/vehicleCabinFit.js +55 -0
- package/dist/visualQa/vehicleCabinFit.js.map +1 -0
- package/dist/visualQa/vehicleClearance.d.ts +135 -0
- package/dist/visualQa/vehicleClearance.js +299 -0
- package/dist/visualQa/vehicleClearance.js.map +1 -0
- package/dist/visualQa/vehicleDriveAlive.d.ts +55 -0
- package/dist/visualQa/vehicleDriveAlive.js +103 -0
- package/dist/visualQa/vehicleDriveAlive.js.map +1 -0
- package/dist/visualQa/vehicleGrip.d.ts +156 -0
- package/dist/visualQa/vehicleGrip.js +439 -0
- package/dist/visualQa/vehicleGrip.js.map +1 -0
- package/dist/visualQa/vehicleInspection.d.ts +70 -0
- package/dist/visualQa/vehicleInspection.js +318 -0
- package/dist/visualQa/vehicleInspection.js.map +1 -0
- package/dist/visualQa/vehicleLamps.d.ts +120 -0
- package/dist/visualQa/vehicleLamps.js +377 -0
- package/dist/visualQa/vehicleLamps.js.map +1 -0
- package/dist/visualQa/vehicleMath.d.ts +440 -0
- package/dist/visualQa/vehicleMath.js +967 -0
- package/dist/visualQa/vehicleMath.js.map +1 -0
- package/dist/visualQa/vehicleOccupants.d.ts +66 -0
- package/dist/visualQa/vehicleOccupants.js +448 -0
- package/dist/visualQa/vehicleOccupants.js.map +1 -0
- package/dist/visualQa/vehiclePenetration.d.ts +172 -0
- package/dist/visualQa/vehiclePenetration.js +1157 -0
- package/dist/visualQa/vehiclePenetration.js.map +1 -0
- package/dist/visualQa/vehiclePoses.d.ts +25 -0
- package/dist/visualQa/vehiclePoses.js +56 -0
- package/dist/visualQa/vehiclePoses.js.map +1 -0
- package/dist/visualQa/vehiclePosture.d.ts +78 -0
- package/dist/visualQa/vehiclePosture.js +263 -0
- package/dist/visualQa/vehiclePosture.js.map +1 -0
- package/dist/visualQa/vehiclePreview.d.ts +103 -0
- package/dist/visualQa/vehiclePreview.js +312 -0
- package/dist/visualQa/vehiclePreview.js.map +1 -0
- package/dist/visualQa/vehicleProbe.d.ts +3 -0
- package/dist/visualQa/vehicleProbe.js +670 -0
- package/dist/visualQa/vehicleProbe.js.map +1 -0
- package/dist/visualQa/vehicleStance.d.ts +108 -0
- package/dist/visualQa/vehicleStance.js +200 -0
- package/dist/visualQa/vehicleStance.js.map +1 -0
- package/dist/visualQa/viewer.d.ts +6 -0
- package/dist/visualQa/viewer.js +750 -0
- package/dist/visualQa/viewer.js.map +1 -0
- package/dist/visualQa/viewerGeometry.d.ts +116 -0
- package/dist/visualQa/viewerGeometry.js +476 -0
- package/dist/visualQa/viewerGeometry.js.map +1 -0
- package/dist/visualQa/viewerPage.d.ts +11 -0
- package/dist/visualQa/viewerPage.js +414 -0
- package/dist/visualQa/viewerPage.js.map +1 -0
- package/dist/visualQa/webgpuSelfTest.d.ts +52 -0
- package/dist/visualQa/webgpuSelfTest.js +113 -0
- package/dist/visualQa/webgpuSelfTest.js.map +1 -0
- package/dist/visualQa/winChrome.d.ts +92 -0
- package/dist/visualQa/winChrome.js +445 -0
- package/dist/visualQa/winChrome.js.map +1 -0
- package/dist/visualQa/worldWalk.d.ts +59 -0
- package/dist/visualQa/worldWalk.js +523 -0
- package/dist/visualQa/worldWalk.js.map +1 -0
- package/dist/visualQa/worldWalkMath.d.ts +263 -0
- package/dist/visualQa/worldWalkMath.js +873 -0
- package/dist/visualQa/worldWalkMath.js.map +1 -0
- package/dist/visualQa/worldWalkProbe.d.ts +1 -0
- package/dist/visualQa/worldWalkProbe.js +206 -0
- package/dist/visualQa/worldWalkProbe.js.map +1 -0
- package/dist/visualsConfig.d.ts +65 -0
- package/dist/visualsConfig.js +1333 -0
- package/dist/visualsConfig.js.map +1 -0
- package/dist/voiceCommand.d.ts +11 -0
- package/dist/voiceCommand.js +101 -0
- package/dist/voiceCommand.js.map +1 -0
- package/dist/world/analyze.d.ts +51 -0
- package/dist/world/analyze.js +767 -0
- package/dist/world/analyze.js.map +1 -0
- package/dist/world/baseline.d.ts +33 -0
- package/dist/world/baseline.js +111 -0
- package/dist/world/baseline.js.map +1 -0
- package/dist/world/canonicalize.d.ts +7 -0
- package/dist/world/canonicalize.js +110 -0
- package/dist/world/canonicalize.js.map +1 -0
- package/dist/world/diff.d.ts +18 -0
- package/dist/world/diff.js +58 -0
- package/dist/world/diff.js.map +1 -0
- package/dist/world/focus.d.ts +11 -0
- package/dist/world/focus.js +161 -0
- package/dist/world/focus.js.map +1 -0
- package/dist/world/format.d.ts +6 -0
- package/dist/world/format.js +125 -0
- package/dist/world/format.js.map +1 -0
- package/dist/world/readWorldMetrics.d.ts +10 -0
- package/dist/world/readWorldMetrics.js +31 -0
- package/dist/world/readWorldMetrics.js.map +1 -0
- package/dist/world/snapshotTypes.d.ts +59 -0
- package/dist/world/snapshotTypes.js +8 -0
- package/dist/world/snapshotTypes.js.map +1 -0
- package/dist/world/snapshotWorld.d.ts +16 -0
- package/dist/world/snapshotWorld.js +142 -0
- package/dist/world/snapshotWorld.js.map +1 -0
- package/dist/world/wiring.d.ts +21 -0
- package/dist/world/wiring.js +73 -0
- package/dist/world/wiring.js.map +1 -0
- package/dist/world/zones.d.ts +23 -0
- package/dist/world/zones.js +134 -0
- package/dist/world/zones.js.map +1 -0
- package/docs/bridge.md +304 -0
- package/docs/device-hand-reach.md +44 -0
- package/docs/scene-hlod.md +139 -0
- package/docs/vehicle-audio-import.md +299 -0
- package/docs/vehicle-simulate.md +165 -0
- package/docs/visual-qa-candidate-versions.md +71 -0
- package/docs/visual-qa.md +462 -0
- package/package.json +69 -4
- package/plugins/beamng-vehicle/README.md +129 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_sim_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/beamng-vehicle.mjs +1295 -0
- package/plugins/beamng-vehicle/bin/beamng_abs.py +408 -0
- package/plugins/beamng-vehicle/bin/beamng_audio.py +394 -0
- package/plugins/beamng-vehicle/bin/beamng_engine.py +184 -0
- package/plugins/beamng-vehicle/bin/beamng_expression.py +373 -0
- package/plugins/beamng-vehicle/bin/beamng_jbeam.py +858 -0
- package/plugins/beamng-vehicle/bin/beamng_materials.py +261 -0
- package/plugins/beamng-vehicle/bin/beamng_stock_events.py +171 -0
- package/plugins/beamng-vehicle/bin/convert-beamng.py +1563 -0
- package/plugins/beamng-vehicle/bin/fmod_studio.py +524 -0
- package/plugins/beamng-vehicle/bin/fsb5.py +304 -0
- package/plugins/beamng-vehicle/bin/index-beamng-common.mjs +16 -0
- package/plugins/beamng-vehicle/bin/index-beamng.py +69 -0
- package/plugins/beamng-vehicle/bin/lower-beamng.py +2399 -0
- package/plugins/beamng-vehicle/bin/sim_audio.py +250 -0
- package/plugins/beamng-vehicle/bin/sim_audio_ac.py +378 -0
- package/plugins/beamng-vehicle/bin/sim_audio_beamng.py +878 -0
- package/plugins/beamng-vehicle/bin/sim_audio_fixtures.py +287 -0
- package/plugins/beamng-vehicle/bin/sim_audio_import.py +135 -0
- package/plugins/beamng-vehicle/bin/test_beamng_engine.py +124 -0
- package/plugins/beamng-vehicle/bin/test_beamng_jbeam.py +383 -0
- package/plugins/beamng-vehicle/bin/test_beamng_precedence.py +134 -0
- package/plugins/beamng-vehicle/bin/test_sim_audio.py +249 -0
- package/plugins/beamng-vehicle/bridge-plugin.json +55 -0
- package/plugins/beamng-vehicle/controller-adapters/beamng-0.39.4-abs.json +52 -0
- package/plugins/beamng-vehicle/lib/common-dependency.mjs +357 -0
- package/plugins/beamng-vehicle/lib/dae-scan.mjs +40 -0
- package/plugins/beamng-vehicle/lib/file-identity.mjs +46 -0
- package/plugins/beamng-vehicle/lib/material-semantics.mjs +312 -0
- package/plugins/beamng-vehicle/lib/palette-contract.mjs +6 -0
- package/plugins/beamng-vehicle/lib/vehicle-qa.mjs +567 -0
- package/plugins/beamng-vehicle/lib/vehicle-source.mjs +247 -0
- package/plugins/beamng-vehicle/profiles/source-compatibility.json +14 -0
- package/plugins/beamng-vehicle/vendor/vehicle-package-v2.mjs +6451 -0
- package/vendor/README.md +63 -0
- package/vendor/ability-manifest.mjs +527 -6
- package/vendor/audits/asset-credits.mjs +96 -0
- package/vendor/audits/check-loaders.mjs +80 -0
- package/vendor/audits/perf-gate.mjs +759 -0
- package/vendor/audits/prove-live.mjs +307 -0
- package/vendor/audits/source-audit.mjs +530 -0
- package/vendor/audits/world-audit.mjs +439 -0
- package/vendor/capture-body.glb +0 -0
- package/vendor/capture-face.glb +0 -0
- package/vendor/installed-index.mjs +1 -1
- package/vendor/vehicle-package-v2.mjs +6451 -0
package/dist/device.js
ADDED
|
@@ -0,0 +1,2744 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEVICE_TEXT_INPUT_MODES = exports.DEVICE_THUMBNAIL_POLICIES = exports.DEVICE_HOSTS_LIST = exports.DEVICE_HOSTS = exports.DEVICE_TRIGGER_KINDS = exports.DEVICE_HAND_SOCKET_LIST = exports.DEVICE_HAND_SOCKETS = exports.DEVICE_GRAPH_STEP_KINDS = exports.DEVICE_COMPONENT_TYPES = exports.DEVICE_CAPABILITIES = exports.DEVICE_INTERACTION_VERB_LIST = exports.DEVICE_INTERACTION_VERBS = exports.DEVICE_FILENAME = exports.deviceActionAstBudgetError = void 0;
|
|
4
|
+
exports.readDeviceGlbNodeNames = readDeviceGlbNodeNames;
|
|
5
|
+
exports.validateDeviceProject = validateDeviceProject;
|
|
6
|
+
exports.testDeviceProject = testDeviceProject;
|
|
7
|
+
exports.createDeviceProject = createDeviceProject;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const deviceActionValidation_1 = require("./deviceActionValidation");
|
|
11
|
+
var deviceActionValidation_2 = require("./deviceActionValidation");
|
|
12
|
+
Object.defineProperty(exports, "deviceActionAstBudgetError", { enumerable: true, get: function () { return deviceActionValidation_2.deviceActionAstBudgetError; } });
|
|
13
|
+
exports.DEVICE_FILENAME = 'device.json';
|
|
14
|
+
/**
|
|
15
|
+
* First-party canvas renderers the web host ships.
|
|
16
|
+
*
|
|
17
|
+
* A `canvas` app is drawn by HOST code into an offscreen 2D context and bound
|
|
18
|
+
* as a texture on the mesh, so the screen is depth-tested and lit like any
|
|
19
|
+
* other surface instead of being composited over the scene. `entry` therefore
|
|
20
|
+
* names one of these ids, never a URL: a package selects first-party drawing
|
|
21
|
+
* code exactly as `localWeb` selects a first-party app path, and can never
|
|
22
|
+
* ship the code itself.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors `DEVICE_CANVAS_APP_IDS` in the web runtime
|
|
25
|
+
* (`helixgame.com/lib/devices/canvas-apps.ts`). The CLI validates offline, so
|
|
26
|
+
* the list is duplicated deliberately; add ids to both.
|
|
27
|
+
*/
|
|
28
|
+
const DEVICE_CANVAS_APPS = new Set(['helix.whiteboard/1']);
|
|
29
|
+
/**
|
|
30
|
+
* The closed interaction verb vocabulary the web host enforces.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the `verb` enum on `DeviceInteraction` in the first-party Device
|
|
33
|
+
* schema (`helixgame.com/lib/devices/validate.ts`). The host REJECTS anything
|
|
34
|
+
* else when the package loads, so validating it only in the host let a package
|
|
35
|
+
* pass `helix device validate` offline and then fail in the client —
|
|
36
|
+
* `continuum-soju` shipped `"verb": "drink"` that way. The list is duplicated
|
|
37
|
+
* deliberately; add verbs to both.
|
|
38
|
+
*/
|
|
39
|
+
exports.DEVICE_INTERACTION_VERBS = [
|
|
40
|
+
'use',
|
|
41
|
+
'open',
|
|
42
|
+
'close',
|
|
43
|
+
'toggle',
|
|
44
|
+
'play',
|
|
45
|
+
'sit',
|
|
46
|
+
'custom',
|
|
47
|
+
];
|
|
48
|
+
/** The same vocabulary rendered as one readable list for error messages. */
|
|
49
|
+
exports.DEVICE_INTERACTION_VERB_LIST = exports.DEVICE_INTERACTION_VERBS.join('|');
|
|
50
|
+
/**
|
|
51
|
+
* The closed capability vocabulary a package may declare.
|
|
52
|
+
*
|
|
53
|
+
* Mirrors `DEVICE_CAPABILITIES` in `helixgame.com/lib/devices/types.ts`. A
|
|
54
|
+
* capability the client accepts and this list omits is a FALSE REJECTION —
|
|
55
|
+
* `physics.solid` was exactly that, and it made the shipped
|
|
56
|
+
* `continuum-garage-door` example (which needs it for its `collision` block)
|
|
57
|
+
* fail `helix device validate` while the client loaded it happily. Add
|
|
58
|
+
* capabilities to both lists, never to one.
|
|
59
|
+
*/
|
|
60
|
+
exports.DEVICE_CAPABILITIES = [
|
|
61
|
+
'model.glb',
|
|
62
|
+
'presentation.nodeMotion',
|
|
63
|
+
'clock.serverTime',
|
|
64
|
+
'state.typed',
|
|
65
|
+
'state.durable',
|
|
66
|
+
'room.authority',
|
|
67
|
+
'audio.playback',
|
|
68
|
+
'presentation.light',
|
|
69
|
+
'presentation.vfx',
|
|
70
|
+
'character.posture',
|
|
71
|
+
'character.animate',
|
|
72
|
+
'character.hand',
|
|
73
|
+
'signals.local',
|
|
74
|
+
'screen.render',
|
|
75
|
+
'screen.input',
|
|
76
|
+
'app.web.local',
|
|
77
|
+
'app.web.remote',
|
|
78
|
+
'app.web.panel',
|
|
79
|
+
'app.image.remote',
|
|
80
|
+
'app.image.owner',
|
|
81
|
+
'app.canvas',
|
|
82
|
+
'device.ports',
|
|
83
|
+
'physics.solid',
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* The closed component vocabulary.
|
|
87
|
+
*
|
|
88
|
+
* Mirrors the discriminated union on `components` in the client schema
|
|
89
|
+
* (`validate.ts:423-527`). Branching on an unknown `type` used to fall through
|
|
90
|
+
* with no check at all, so a component kind this list does not name is rejected
|
|
91
|
+
* here rather than validated as an empty object.
|
|
92
|
+
*/
|
|
93
|
+
exports.DEVICE_COMPONENT_TYPES = [
|
|
94
|
+
'slider',
|
|
95
|
+
'clockHands',
|
|
96
|
+
'pointLight',
|
|
97
|
+
'audioReactiveLight',
|
|
98
|
+
'characterSpot',
|
|
99
|
+
'screen',
|
|
100
|
+
];
|
|
101
|
+
/**
|
|
102
|
+
* The closed behavior-graph step vocabulary.
|
|
103
|
+
*
|
|
104
|
+
* Mirrors the `step` union in the client schema (`validate.ts:133-222`). Every
|
|
105
|
+
* kind is validated to the client's depth, including the ones the shipped
|
|
106
|
+
* examples never use (`moveNode`, `setVfx`, `emitSignal`, `sequence`, `delay`,
|
|
107
|
+
* `repeat`, `stopAudio`) — their absence from the examples is why the gap went
|
|
108
|
+
* unnoticed for so long.
|
|
109
|
+
*/
|
|
110
|
+
exports.DEVICE_GRAPH_STEP_KINDS = [
|
|
111
|
+
'setState',
|
|
112
|
+
'toggleState',
|
|
113
|
+
'moveNode',
|
|
114
|
+
'rotateNode',
|
|
115
|
+
'playAudio',
|
|
116
|
+
'stopAudio',
|
|
117
|
+
'setVfx',
|
|
118
|
+
'attachCharacter',
|
|
119
|
+
'playCharacterAnimation',
|
|
120
|
+
'releaseCharacter',
|
|
121
|
+
'enterCharacterSpot',
|
|
122
|
+
'reachCharacterHand',
|
|
123
|
+
'releaseCharacterHand',
|
|
124
|
+
'attachItem',
|
|
125
|
+
'releaseItem',
|
|
126
|
+
'emitSignal',
|
|
127
|
+
'branch',
|
|
128
|
+
'sequence',
|
|
129
|
+
'delay',
|
|
130
|
+
'repeat',
|
|
131
|
+
];
|
|
132
|
+
/**
|
|
133
|
+
* The closed CHARACTER socket set an `attachItem` step may target.
|
|
134
|
+
*
|
|
135
|
+
* These are sockets on the acting character, never the Device's own `sockets`:
|
|
136
|
+
* the two namespaces are unrelated, and a Device that mounts on a wall has no
|
|
137
|
+
* hand to put anything in. Mirrors `DEVICE_HAND_SOCKETS` in the first-party
|
|
138
|
+
* host (`helixgame.com/lib/devices/types.ts`) and in the portable engine. The
|
|
139
|
+
* CLI validates offline, so the list is duplicated deliberately; add sockets
|
|
140
|
+
* to both.
|
|
141
|
+
*/
|
|
142
|
+
exports.DEVICE_HAND_SOCKETS = ['hand_r.grip', 'hand_l.grip'];
|
|
143
|
+
/** The same vocabulary rendered as one readable list for error messages. */
|
|
144
|
+
exports.DEVICE_HAND_SOCKET_LIST = exports.DEVICE_HAND_SOCKETS.join('|');
|
|
145
|
+
/** The closed trigger vocabulary (`validate.ts:224-243`). */
|
|
146
|
+
exports.DEVICE_TRIGGER_KINDS = [
|
|
147
|
+
'init',
|
|
148
|
+
'interact',
|
|
149
|
+
'stateChanged',
|
|
150
|
+
'signal',
|
|
151
|
+
'playerNear',
|
|
152
|
+
];
|
|
153
|
+
/** The closed host vocabulary for `compatibility.hosts` (`validate.ts:555-560`). */
|
|
154
|
+
exports.DEVICE_HOSTS = ['home', 'world'];
|
|
155
|
+
/** The same vocabulary rendered as one readable list for error messages. */
|
|
156
|
+
exports.DEVICE_HOSTS_LIST = exports.DEVICE_HOSTS.join('|');
|
|
157
|
+
/** The closed `thumbnail.policy` vocabulary (`validate.ts:563-567`). */
|
|
158
|
+
exports.DEVICE_THUMBNAIL_POLICIES = [
|
|
159
|
+
'catalog',
|
|
160
|
+
'discreteState',
|
|
161
|
+
'never',
|
|
162
|
+
];
|
|
163
|
+
const INTERACTION_VERBS = new Set(exports.DEVICE_INTERACTION_VERBS);
|
|
164
|
+
const CAPABILITIES = new Set(exports.DEVICE_CAPABILITIES);
|
|
165
|
+
const COMPONENT_TYPES = new Set(exports.DEVICE_COMPONENT_TYPES);
|
|
166
|
+
const GRAPH_STEP_KINDS = new Set(exports.DEVICE_GRAPH_STEP_KINDS);
|
|
167
|
+
const TRIGGER_KINDS = new Set(exports.DEVICE_TRIGGER_KINDS);
|
|
168
|
+
const HAND_SOCKETS = new Set(exports.DEVICE_HAND_SOCKETS);
|
|
169
|
+
const CHARACTER_HANDS = new Set(['left', 'right']);
|
|
170
|
+
const CHARACTER_HAND_ELBOWS = new Set(['down', 'out', 'up']);
|
|
171
|
+
const DEVICE_HOST_SET = new Set(exports.DEVICE_HOSTS);
|
|
172
|
+
const THUMBNAIL_POLICIES = new Set(exports.DEVICE_THUMBNAIL_POLICIES);
|
|
173
|
+
/**
|
|
174
|
+
* Device budgets, mirrored from `DEVICE_BUDGETS` in the client's
|
|
175
|
+
* `types.ts:752-764`. The client enforces every one of these post-parse, so a
|
|
176
|
+
* definition over budget is CLI-green and load-rejected.
|
|
177
|
+
*/
|
|
178
|
+
const DEVICE_BUDGETS = {
|
|
179
|
+
maxStateVars: 32,
|
|
180
|
+
maxPersistentVars: 16,
|
|
181
|
+
maxEnumValues: 16,
|
|
182
|
+
maxInteractions: 12,
|
|
183
|
+
maxTriggers: 64,
|
|
184
|
+
maxComponents: 32,
|
|
185
|
+
maxPorts: 32,
|
|
186
|
+
maxInterfaces: 16,
|
|
187
|
+
maxCollisionNodes: 8,
|
|
188
|
+
maxCapabilities: 16,
|
|
189
|
+
maxThumbnailStateKeys: 16,
|
|
190
|
+
maxScreens: 4,
|
|
191
|
+
maxPointLights: 4,
|
|
192
|
+
maxStepsPerFire: 256,
|
|
193
|
+
maxConcurrentDelays: 8,
|
|
194
|
+
};
|
|
195
|
+
const DEVICE_EASINGS = new Set(['linear', 'easeOutCubic', 'easeInOutCubic']);
|
|
196
|
+
const DEVICE_GRAPH_EXPR_KINDS = new Set([
|
|
197
|
+
'lit',
|
|
198
|
+
'state',
|
|
199
|
+
'add',
|
|
200
|
+
'sub',
|
|
201
|
+
'mul',
|
|
202
|
+
'min',
|
|
203
|
+
'max',
|
|
204
|
+
]);
|
|
205
|
+
const DEVICE_GRAPH_CONDITION_OPS = new Set([
|
|
206
|
+
'true',
|
|
207
|
+
'false',
|
|
208
|
+
'and',
|
|
209
|
+
'or',
|
|
210
|
+
'not',
|
|
211
|
+
'eq',
|
|
212
|
+
'ne',
|
|
213
|
+
'gt',
|
|
214
|
+
'lt',
|
|
215
|
+
'gte',
|
|
216
|
+
'lte',
|
|
217
|
+
'inRange',
|
|
218
|
+
]);
|
|
219
|
+
const DEVICE_FFT_SIZES = new Set([32, 64, 128, 256, 512, 1024, 2048]);
|
|
220
|
+
/** The host's `inputMode` vocabulary for a text control (`validate.ts:414-419`). */
|
|
221
|
+
exports.DEVICE_TEXT_INPUT_MODES = [
|
|
222
|
+
'text',
|
|
223
|
+
'tel',
|
|
224
|
+
'url',
|
|
225
|
+
'email',
|
|
226
|
+
'numeric',
|
|
227
|
+
'decimal',
|
|
228
|
+
'search',
|
|
229
|
+
];
|
|
230
|
+
const TEXT_INPUT_MODES = new Set(exports.DEVICE_TEXT_INPUT_MODES);
|
|
231
|
+
const DEVICE_APP_PERMISSIONS = new Set([
|
|
232
|
+
'web.content',
|
|
233
|
+
'network.fetch',
|
|
234
|
+
'web.externalNavigation',
|
|
235
|
+
'audio.media',
|
|
236
|
+
]);
|
|
237
|
+
const DEVICE_PORT_DIRECTIONS = new Set(['input', 'output', 'bidirectional']);
|
|
238
|
+
const DEVICE_PORT_AUTHORITIES = new Set(['local', 'server']);
|
|
239
|
+
const DEVICE_STREAM_ROUTABILITY = new Set([
|
|
240
|
+
'routable',
|
|
241
|
+
'embedded-only',
|
|
242
|
+
'unavailable',
|
|
243
|
+
]);
|
|
244
|
+
function object(value) {
|
|
245
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
246
|
+
? value
|
|
247
|
+
: {};
|
|
248
|
+
}
|
|
249
|
+
function isRecord(value) {
|
|
250
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
251
|
+
}
|
|
252
|
+
function scalar(value) {
|
|
253
|
+
return (typeof value === 'number' ||
|
|
254
|
+
typeof value === 'string' ||
|
|
255
|
+
typeof value === 'boolean');
|
|
256
|
+
}
|
|
257
|
+
function isVec3(value) {
|
|
258
|
+
return (Array.isArray(value) &&
|
|
259
|
+
value.length === 3 &&
|
|
260
|
+
value.every((part) => typeof part === 'number' && Number.isFinite(part)));
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The looser triple the host writes inline on a procedural part
|
|
264
|
+
* (`z.tuple([z.number(), z.number(), z.number()])`) — no `.finite()`
|
|
265
|
+
* refinement, unlike its `vec3` alias. Mirrored exactly so a part transform
|
|
266
|
+
* the host accepts is never refused here.
|
|
267
|
+
*/
|
|
268
|
+
function isNumberTriple(value) {
|
|
269
|
+
return (Array.isArray(value) &&
|
|
270
|
+
value.length === 3 &&
|
|
271
|
+
value.every((part) => typeof part === 'number' && !Number.isNaN(part)));
|
|
272
|
+
}
|
|
273
|
+
function isFiniteNumber(value) {
|
|
274
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
275
|
+
}
|
|
276
|
+
function isNonEmptyName(value) {
|
|
277
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
278
|
+
}
|
|
279
|
+
/** A node reference resolved through `model.nodeBindings`, as the host does. */
|
|
280
|
+
function graphNode(context, raw, path) {
|
|
281
|
+
const semantic = String(raw ?? '');
|
|
282
|
+
const authored = String(context.bindings[semantic] ?? semantic);
|
|
283
|
+
if (context.nodesKnown && !context.nodes.has(authored)) {
|
|
284
|
+
context.errors.push(`${path} resolves to missing GLB node "${authored}"`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function checkEasing(value, path, errors) {
|
|
288
|
+
if (value !== undefined && !DEVICE_EASINGS.has(String(value))) {
|
|
289
|
+
errors.push(`${path}: easing "${String(value)}" is unsupported; expected one of ${[...DEVICE_EASINGS].join('|')}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function validateGraphExpr(raw, context, path) {
|
|
293
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
294
|
+
context.errors.push(`${path}: expression must be an object`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const node = object(raw);
|
|
298
|
+
const kind = String(node.kind ?? '');
|
|
299
|
+
if (!DEVICE_GRAPH_EXPR_KINDS.has(kind)) {
|
|
300
|
+
context.errors.push(`${path}: unsupported expression kind "${kind}" (one of ${[...DEVICE_GRAPH_EXPR_KINDS].join('|')})`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (kind === 'lit') {
|
|
304
|
+
if (!scalar(node.value) || (typeof node.value === 'number' && !Number.isFinite(node.value))) {
|
|
305
|
+
context.errors.push(`${path}: lit requires a finite scalar value`);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (kind === 'state') {
|
|
310
|
+
if (!isNonEmptyName(node.key)) {
|
|
311
|
+
context.errors.push(`${path}: state expression requires a non-empty key`);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const args = Array.isArray(node.args) ? node.args : [];
|
|
316
|
+
if (args.length < 1) {
|
|
317
|
+
context.errors.push(`${path}: ${kind} requires at least one argument`);
|
|
318
|
+
}
|
|
319
|
+
args.forEach((arg, index) => validateGraphExpr(arg, context, `${path}.args.${index}`));
|
|
320
|
+
}
|
|
321
|
+
function validateGraphCondition(raw, context, path) {
|
|
322
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
323
|
+
context.errors.push(`${path}: condition must be an object`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const node = object(raw);
|
|
327
|
+
const op = String(node.op ?? '');
|
|
328
|
+
if (!DEVICE_GRAPH_CONDITION_OPS.has(op)) {
|
|
329
|
+
context.errors.push(`${path}: unsupported condition op "${op}" (one of ${[...DEVICE_GRAPH_CONDITION_OPS].join('|')})`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (op === 'true' || op === 'false')
|
|
333
|
+
return;
|
|
334
|
+
if (op === 'and' || op === 'or') {
|
|
335
|
+
const parts = Array.isArray(node.of) ? node.of : [];
|
|
336
|
+
if (parts.length < 1) {
|
|
337
|
+
context.errors.push(`${path}: ${op} requires at least one condition`);
|
|
338
|
+
}
|
|
339
|
+
parts.forEach((part, index) => validateGraphCondition(part, context, `${path}.of.${index}`));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (op === 'not') {
|
|
343
|
+
validateGraphCondition(node.of, context, `${path}.of`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (op === 'inRange') {
|
|
347
|
+
validateGraphExpr(node.value, context, `${path}.value`);
|
|
348
|
+
validateGraphExpr(node.min, context, `${path}.min`);
|
|
349
|
+
validateGraphExpr(node.max, context, `${path}.max`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
validateGraphExpr(node.left, context, `${path}.left`);
|
|
353
|
+
validateGraphExpr(node.right, context, `${path}.right`);
|
|
354
|
+
}
|
|
355
|
+
const DEVICE_GRAPH_MAX_BRANCH_DEPTH = 8;
|
|
356
|
+
/**
|
|
357
|
+
* Validate one behavior-graph step list, recursing into every nested list.
|
|
358
|
+
*
|
|
359
|
+
* The client's `stepSchema` (`validate.ts:133-222`) is a closed union and
|
|
360
|
+
* this walks all of them; validating only the four character steps let ten
|
|
361
|
+
* kinds — including `moveNode`, `setVfx`, `emitSignal` and `delay` — ship
|
|
362
|
+
* entirely unchecked, so a missing `durationMs` or a `seconds: 4000` delay
|
|
363
|
+
* passed `helix device validate` and was rejected when the package loaded.
|
|
364
|
+
*/
|
|
365
|
+
function validateGraphSteps(steps, context, path, depth) {
|
|
366
|
+
if (!Array.isArray(steps)) {
|
|
367
|
+
context.errors.push(`${path}: graph steps must be an array`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (depth > DEVICE_GRAPH_MAX_BRANCH_DEPTH) {
|
|
371
|
+
context.errors.push(`${path}: graph nesting exceeds ${DEVICE_GRAPH_MAX_BRANCH_DEPTH} levels`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
steps.forEach((raw, index) => {
|
|
375
|
+
const stepPath = `${path}.${index}`;
|
|
376
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
377
|
+
context.errors.push(`${stepPath}: graph step must be an object`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const step = object(raw);
|
|
381
|
+
const kind = String(step.step ?? '');
|
|
382
|
+
if (!GRAPH_STEP_KINDS.has(kind)) {
|
|
383
|
+
context.errors.push(`${stepPath}: unsupported graph step "${kind}" (one of ${exports.DEVICE_GRAPH_STEP_KINDS.join('|')})`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const requireName = (field) => {
|
|
387
|
+
if (!isNonEmptyName(step[field])) {
|
|
388
|
+
context.errors.push(`${stepPath}: ${kind} requires a non-empty ${field}`);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
};
|
|
393
|
+
switch (kind) {
|
|
394
|
+
case 'setState':
|
|
395
|
+
case 'toggleState': {
|
|
396
|
+
requireName('key');
|
|
397
|
+
for (const field of ['value', 'delta']) {
|
|
398
|
+
if (step[field] !== undefined)
|
|
399
|
+
validateGraphExpr(step[field], context, `${stepPath}.${field}`);
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
case 'moveNode': {
|
|
404
|
+
requireName('node');
|
|
405
|
+
graphNode(context, step.node, stepPath);
|
|
406
|
+
if (!['x', 'y', 'z'].includes(String(step.property)))
|
|
407
|
+
context.errors.push(`${stepPath}: moveNode property must be x, y or z`);
|
|
408
|
+
if (!isFiniteNumber(step.durationMs)) {
|
|
409
|
+
context.errors.push(`${stepPath}: moveNode requires a finite durationMs`);
|
|
410
|
+
}
|
|
411
|
+
for (const field of ['to', 'by']) {
|
|
412
|
+
if (step[field] !== undefined)
|
|
413
|
+
validateGraphExpr(step[field], context, `${stepPath}.${field}`);
|
|
414
|
+
}
|
|
415
|
+
checkEasing(step.easing, stepPath, context.errors);
|
|
416
|
+
context.usage.nodeMotion = true;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case 'rotateNode': {
|
|
420
|
+
requireName('node');
|
|
421
|
+
graphNode(context, step.node, stepPath);
|
|
422
|
+
if (!['x', 'y', 'z'].includes(String(step.axis)))
|
|
423
|
+
context.errors.push(`${stepPath}: rotateNode axis must be x, y or z`);
|
|
424
|
+
if (!['toAngle', 'stop'].includes(String(step.mode)))
|
|
425
|
+
context.errors.push(`${stepPath}: rotateNode mode must be toAngle or stop`);
|
|
426
|
+
if (step.targetDeg !== undefined)
|
|
427
|
+
validateGraphExpr(step.targetDeg, context, `${stepPath}.targetDeg`);
|
|
428
|
+
if (step.durationMs !== undefined && !isFiniteNumber(step.durationMs))
|
|
429
|
+
context.errors.push(`${stepPath}: rotateNode durationMs must be a finite number`);
|
|
430
|
+
checkEasing(step.easing, stepPath, context.errors);
|
|
431
|
+
context.usage.nodeMotion = true;
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
case 'playAudio':
|
|
435
|
+
case 'stopAudio': {
|
|
436
|
+
const hasAsset = isNonEmptyName(step.asset);
|
|
437
|
+
const hasUrlStateKey = isNonEmptyName(step.urlStateKey);
|
|
438
|
+
if (step.asset !== undefined && !hasAsset)
|
|
439
|
+
context.errors.push(`${stepPath}: playAudio asset must be non-empty`);
|
|
440
|
+
if (step.urlStateKey !== undefined && !hasUrlStateKey)
|
|
441
|
+
context.errors.push(`${stepPath}: audio urlStateKey must be non-empty`);
|
|
442
|
+
if (hasAsset === hasUrlStateKey) {
|
|
443
|
+
context.errors.push('graph: audio step must declare exactly one of asset or urlStateKey');
|
|
444
|
+
}
|
|
445
|
+
if (hasAsset && !context.audio.has(String(step.asset)))
|
|
446
|
+
context.errors.push(`graph: unknown audio asset "${String(step.asset)}"`);
|
|
447
|
+
if (hasUrlStateKey) {
|
|
448
|
+
const spec = object(context.state[String(step.urlStateKey)]);
|
|
449
|
+
if (spec.type !== 'string') {
|
|
450
|
+
context.errors.push(`graph: audio urlStateKey "${String(step.urlStateKey)}" must name string state`);
|
|
451
|
+
}
|
|
452
|
+
else if (spec.maxLen === undefined || Number(spec.maxLen) > 1024) {
|
|
453
|
+
context.errors.push(`graph: audio urlStateKey "${String(step.urlStateKey)}" must be bounded to 1024 characters`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (step.at !== undefined) {
|
|
457
|
+
if (!isNonEmptyName(step.at))
|
|
458
|
+
context.errors.push(`${stepPath}: audio at must be non-empty`);
|
|
459
|
+
else if (!context.sockets.has(String(step.at)))
|
|
460
|
+
context.errors.push(`graph: unknown audio socket "${String(step.at)}"`);
|
|
461
|
+
}
|
|
462
|
+
if (step.loop !== undefined && typeof step.loop !== 'boolean')
|
|
463
|
+
context.errors.push(`${stepPath}: playAudio loop must be boolean`);
|
|
464
|
+
if (step.volume !== undefined &&
|
|
465
|
+
(!isFiniteNumber(step.volume) || step.volume < 0 || step.volume > 1)) {
|
|
466
|
+
context.errors.push(`${stepPath}: playAudio volume must be between 0 and 1`);
|
|
467
|
+
}
|
|
468
|
+
context.usage.audioPlayback = true;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
case 'setVfx': {
|
|
472
|
+
requireName('effect');
|
|
473
|
+
if (typeof step.on !== 'boolean') {
|
|
474
|
+
context.errors.push(`${stepPath}: setVfx requires a boolean on`);
|
|
475
|
+
}
|
|
476
|
+
if (isNonEmptyName(step.effect) && !context.effects.has(String(step.effect)))
|
|
477
|
+
context.errors.push(`graph: unknown VFX "${String(step.effect)}"`);
|
|
478
|
+
if (step.at !== undefined) {
|
|
479
|
+
if (!isNonEmptyName(step.at))
|
|
480
|
+
context.errors.push(`${stepPath}: setVfx at must be non-empty`);
|
|
481
|
+
else if (!context.sockets.has(String(step.at)))
|
|
482
|
+
context.errors.push(`graph: unknown VFX socket "${String(step.at)}"`);
|
|
483
|
+
}
|
|
484
|
+
if (step.durationSeconds !== undefined &&
|
|
485
|
+
(!isFiniteNumber(step.durationSeconds) ||
|
|
486
|
+
step.durationSeconds < 0 ||
|
|
487
|
+
step.durationSeconds > 3600)) {
|
|
488
|
+
context.errors.push(`${stepPath}: setVfx durationSeconds must be between 0 and 3600`);
|
|
489
|
+
}
|
|
490
|
+
context.usage.vfx = true;
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
case 'attachCharacter': {
|
|
494
|
+
requireName('socket');
|
|
495
|
+
if (isNonEmptyName(step.socket) && !context.sockets.has(String(step.socket)))
|
|
496
|
+
context.errors.push(`graph: unknown character socket "${String(step.socket)}"`);
|
|
497
|
+
context.usage.animation = true;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
case 'playCharacterAnimation': {
|
|
501
|
+
requireName('asset');
|
|
502
|
+
if (isNonEmptyName(step.asset) &&
|
|
503
|
+
!context.animations.has(String(step.asset))) {
|
|
504
|
+
context.errors.push(`graph: unknown animation asset "${String(step.asset)}"`);
|
|
505
|
+
}
|
|
506
|
+
if (step.loop !== undefined && typeof step.loop !== 'boolean')
|
|
507
|
+
context.errors.push(`${stepPath}: playCharacterAnimation loop must be boolean`);
|
|
508
|
+
context.usage.animation = true;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
case 'releaseCharacter': {
|
|
512
|
+
if (step.exitSocket !== undefined) {
|
|
513
|
+
if (!isNonEmptyName(step.exitSocket)) {
|
|
514
|
+
context.errors.push(`graph releaseCharacter exitSocket must be a non-empty name`);
|
|
515
|
+
}
|
|
516
|
+
else if (!context.sockets.has(String(step.exitSocket))) {
|
|
517
|
+
context.errors.push(`graph: unknown exit socket "${String(step.exitSocket)}"`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
context.usage.animation = true;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
case 'enterCharacterSpot': {
|
|
524
|
+
context.usage.posture = true;
|
|
525
|
+
const component = String(step.component ?? '');
|
|
526
|
+
if (!context.characterSpots.has(component)) {
|
|
527
|
+
context.errors.push(`graph enterCharacterSpot references unknown character spot "${component}"`);
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
case 'reachCharacterHand': {
|
|
532
|
+
context.usage.hand = true;
|
|
533
|
+
const hand = String(step.hand ?? '');
|
|
534
|
+
if (!CHARACTER_HANDS.has(hand)) {
|
|
535
|
+
context.errors.push(`${stepPath}: reachCharacterHand hand must be left or right`);
|
|
536
|
+
}
|
|
537
|
+
if (!step.target ||
|
|
538
|
+
typeof step.target !== 'object' ||
|
|
539
|
+
Array.isArray(step.target)) {
|
|
540
|
+
context.errors.push(`${stepPath}: reachCharacterHand target must be an object with exactly one of node or socket`);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
const target = object(step.target);
|
|
544
|
+
const keys = Object.keys(target);
|
|
545
|
+
const hasNode = Object.prototype.hasOwnProperty.call(target, 'node');
|
|
546
|
+
const hasSocket = Object.prototype.hasOwnProperty.call(target, 'socket');
|
|
547
|
+
if (hasNode === hasSocket ||
|
|
548
|
+
keys.some((key) => key !== 'node' && key !== 'socket')) {
|
|
549
|
+
context.errors.push(`${stepPath}: reachCharacterHand target must declare exactly one of node or socket`);
|
|
550
|
+
}
|
|
551
|
+
else if (hasNode) {
|
|
552
|
+
if (!isNonEmptyName(target.node)) {
|
|
553
|
+
context.errors.push(`${stepPath}: reachCharacterHand target.node must be a non-empty name`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
const semantic = String(target.node);
|
|
557
|
+
if (!context.nodesKnown &&
|
|
558
|
+
!Object.prototype.hasOwnProperty.call(context.bindings, semantic)) {
|
|
559
|
+
context.errors.push(`${stepPath}.target.node must name an authored model.nodeBindings entry when GLB bytes are unavailable`);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
graphNode(context, target.node, `${stepPath}.target.node`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else if (!isNonEmptyName(target.socket)) {
|
|
567
|
+
context.errors.push(`${stepPath}: reachCharacterHand target.socket must be a non-empty name`);
|
|
568
|
+
}
|
|
569
|
+
else if (!context.sockets.has(String(target.socket))) {
|
|
570
|
+
context.errors.push(`${stepPath}: reachCharacterHand references unknown Device socket "${String(target.socket)}"`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (!isFiniteNumber(step.engageDistanceM) ||
|
|
574
|
+
step.engageDistanceM < 0.1 ||
|
|
575
|
+
step.engageDistanceM > 2) {
|
|
576
|
+
context.errors.push(`${stepPath}: reachCharacterHand engageDistanceM must be between 0.1 and 2`);
|
|
577
|
+
}
|
|
578
|
+
if (!isFiniteNumber(step.releaseDistanceM) ||
|
|
579
|
+
step.releaseDistanceM > 3 ||
|
|
580
|
+
(isFiniteNumber(step.engageDistanceM) &&
|
|
581
|
+
step.releaseDistanceM < step.engageDistanceM)) {
|
|
582
|
+
context.errors.push(`${stepPath}: reachCharacterHand releaseDistanceM must be at least engageDistanceM and no more than 3`);
|
|
583
|
+
}
|
|
584
|
+
if (step.maxAngleDeg !== undefined &&
|
|
585
|
+
(!isFiniteNumber(step.maxAngleDeg) ||
|
|
586
|
+
step.maxAngleDeg < 1 ||
|
|
587
|
+
step.maxAngleDeg > 180)) {
|
|
588
|
+
context.errors.push(`${stepPath}: reachCharacterHand maxAngleDeg must be between 1 and 180`);
|
|
589
|
+
}
|
|
590
|
+
if (step.elbow !== undefined &&
|
|
591
|
+
!CHARACTER_HAND_ELBOWS.has(String(step.elbow))) {
|
|
592
|
+
context.errors.push(`${stepPath}: reachCharacterHand elbow must be down, out or up`);
|
|
593
|
+
}
|
|
594
|
+
if (step.orientMatch !== undefined &&
|
|
595
|
+
typeof step.orientMatch !== 'boolean') {
|
|
596
|
+
context.errors.push(`${stepPath}: reachCharacterHand orientMatch must be boolean`);
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
case 'releaseCharacterHand': {
|
|
601
|
+
context.usage.hand = true;
|
|
602
|
+
const hand = String(step.hand ?? '');
|
|
603
|
+
if (!CHARACTER_HANDS.has(hand)) {
|
|
604
|
+
context.errors.push(`${stepPath}: releaseCharacterHand hand must be left or right`);
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
case 'attachItem': {
|
|
609
|
+
// `attachItem` puts the Device's OWN model on the acting character's
|
|
610
|
+
// hand, so it is a hand power rather than an animation one. Its
|
|
611
|
+
// `socket` is a CHARACTER socket from the closed hand set — never one of
|
|
612
|
+
// the Device's own `sockets`, which are a different namespace. `preset`
|
|
613
|
+
// stays free-form: the engine's held-item registry resolves it, and an
|
|
614
|
+
// unknown name degrades to the socket's default frame.
|
|
615
|
+
context.usage.hand = true;
|
|
616
|
+
if (!isNonEmptyName(step.socket)) {
|
|
617
|
+
context.errors.push('graph attachItem requires a non-empty socket');
|
|
618
|
+
}
|
|
619
|
+
else if (!HAND_SOCKETS.has(String(step.socket))) {
|
|
620
|
+
context.errors.push(`graph attachItem socket "${String(step.socket)}" is unsupported; expected one of ${exports.DEVICE_HAND_SOCKET_LIST}`);
|
|
621
|
+
}
|
|
622
|
+
if (step.preset !== undefined && !isNonEmptyName(step.preset))
|
|
623
|
+
context.errors.push('graph attachItem preset must be a non-empty name');
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
case 'releaseItem': {
|
|
627
|
+
context.usage.hand = true;
|
|
628
|
+
if (step.socket !== undefined && !isNonEmptyName(step.socket))
|
|
629
|
+
context.errors.push('graph releaseItem socket must be a non-empty name');
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
case 'emitSignal': {
|
|
633
|
+
if (!isNonEmptyName(step.name) || String(step.name).length > 64) {
|
|
634
|
+
context.errors.push(`${stepPath}: emitSignal name must be 1..64 characters`);
|
|
635
|
+
}
|
|
636
|
+
if (step.payload !== undefined) {
|
|
637
|
+
if (!step.payload || typeof step.payload !== 'object' || Array.isArray(step.payload)) {
|
|
638
|
+
context.errors.push(`${stepPath}: emitSignal payload must be an object`);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
for (const [key, value] of Object.entries(object(step.payload))) {
|
|
642
|
+
if (!key.trim() || !scalar(value)) {
|
|
643
|
+
context.errors.push(`${stepPath}.payload.${key}: payload values must be scalar`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
case 'branch': {
|
|
651
|
+
if (step.when === undefined) {
|
|
652
|
+
context.errors.push(`${stepPath}: branch requires when`);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
validateGraphCondition(step.when, context, `${stepPath}.when`);
|
|
656
|
+
}
|
|
657
|
+
if (step.then === undefined) {
|
|
658
|
+
context.errors.push(`${stepPath}: branch requires then`);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
validateGraphSteps(step.then, context, `${stepPath}.then`, depth + 1);
|
|
662
|
+
}
|
|
663
|
+
if (step.else !== undefined)
|
|
664
|
+
validateGraphSteps(step.else, context, `${stepPath}.else`, depth + 1);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
case 'sequence': {
|
|
668
|
+
if (step.do === undefined) {
|
|
669
|
+
context.errors.push(`${stepPath}: sequence requires do`);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
validateGraphSteps(step.do, context, `${stepPath}.do`, depth + 1);
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
case 'delay': {
|
|
677
|
+
if (!isFiniteNumber(step.seconds) || step.seconds < 0 || step.seconds > 3600) {
|
|
678
|
+
context.errors.push(`${stepPath}: delay seconds must be between 0 and 3600`);
|
|
679
|
+
}
|
|
680
|
+
if (step.then === undefined) {
|
|
681
|
+
context.errors.push(`${stepPath}: delay requires then`);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
validateGraphSteps(step.then, context, `${stepPath}.then`, depth + 1);
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
case 'repeat': {
|
|
689
|
+
if (!Number.isInteger(step.times) || Number(step.times) < 0) {
|
|
690
|
+
context.errors.push(`${stepPath}: repeat times must be a non-negative integer`);
|
|
691
|
+
}
|
|
692
|
+
else if (Number(step.times) > 64) {
|
|
693
|
+
context.errors.push(`${stepPath}: repeat times must not exceed 64`);
|
|
694
|
+
}
|
|
695
|
+
if (step.do === undefined) {
|
|
696
|
+
context.errors.push(`${stepPath}: repeat requires do`);
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
validateGraphSteps(step.do, context, `${stepPath}.do`, depth + 1);
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
default:
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Follow every bounded graph branch to prove that a reach cannot outlive the
|
|
710
|
+
* trigger execution path that created it. A later, unrelated trigger is not a
|
|
711
|
+
* release guarantee: it may never fire. An orphan release is intentionally a
|
|
712
|
+
* no-op so cleanup steps remain idempotent.
|
|
713
|
+
*/
|
|
714
|
+
function handReachPathEnds(steps, starts = [new Set()], depth = 0) {
|
|
715
|
+
const context = {
|
|
716
|
+
nextRelationId: 1,
|
|
717
|
+
relations: new WeakMap(),
|
|
718
|
+
};
|
|
719
|
+
const endings = handUnitEnds(steps, starts.map(handSetToMask), depth, context);
|
|
720
|
+
return endings.map(handMaskToSet);
|
|
721
|
+
}
|
|
722
|
+
function handUnitEnds(steps, starts, depth, context) {
|
|
723
|
+
const paths = walkHandSteps(steps, starts.map((state) => ({ state, delays: [] })), depth, context);
|
|
724
|
+
return dedupeHandMasks(paths.flatMap((path) => finishHandDelays(path.state, path.delays)));
|
|
725
|
+
}
|
|
726
|
+
function walkHandSteps(steps, starts, depth, context) {
|
|
727
|
+
if (!Array.isArray(steps) || depth > DEVICE_GRAPH_MAX_BRANCH_DEPTH) {
|
|
728
|
+
return starts.map(cloneHandPath);
|
|
729
|
+
}
|
|
730
|
+
let paths = starts.map(cloneHandPath);
|
|
731
|
+
for (const raw of steps) {
|
|
732
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
733
|
+
continue;
|
|
734
|
+
const step = object(raw);
|
|
735
|
+
const kind = String(step.step ?? '');
|
|
736
|
+
if (kind === 'reachCharacterHand') {
|
|
737
|
+
const bit = handBit(step.hand);
|
|
738
|
+
if (bit !== 0)
|
|
739
|
+
for (const path of paths)
|
|
740
|
+
path.state |= bit;
|
|
741
|
+
}
|
|
742
|
+
else if (kind === 'releaseCharacterHand') {
|
|
743
|
+
const bit = handBit(step.hand);
|
|
744
|
+
if (bit !== 0)
|
|
745
|
+
for (const path of paths)
|
|
746
|
+
path.state &= ~bit;
|
|
747
|
+
}
|
|
748
|
+
else if (kind === 'branch') {
|
|
749
|
+
const branched = [];
|
|
750
|
+
const condition = object(step.when);
|
|
751
|
+
const arms = condition.op === 'true'
|
|
752
|
+
? [step.then]
|
|
753
|
+
: condition.op === 'false'
|
|
754
|
+
? [step.else ?? []]
|
|
755
|
+
: [step.then, step.else ?? []];
|
|
756
|
+
for (const path of paths) {
|
|
757
|
+
for (const arm of arms) {
|
|
758
|
+
branched.push(...walkHandSteps(arm, [path], depth + 1, context));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
paths = dedupeHandPaths(branched);
|
|
762
|
+
}
|
|
763
|
+
else if (kind === 'sequence') {
|
|
764
|
+
paths = walkHandSteps(step.do, paths, depth + 1, context);
|
|
765
|
+
}
|
|
766
|
+
else if (kind === 'delay') {
|
|
767
|
+
const relation = handRelation(step.then, depth + 1, context);
|
|
768
|
+
for (const path of paths) {
|
|
769
|
+
// The runtime silently drops delay nine while eight are pending. The
|
|
770
|
+
// metric validator rejects that shape; mirroring the drop here keeps
|
|
771
|
+
// path analysis faithful even for an already-invalid graph.
|
|
772
|
+
if (path.delays.length < DEVICE_BUDGETS.maxConcurrentDelays) {
|
|
773
|
+
path.delays.push(relation);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
else if (kind === 'repeat' &&
|
|
778
|
+
Number.isInteger(step.times) &&
|
|
779
|
+
Number(step.times) > 0) {
|
|
780
|
+
for (let index = 0; index < Number(step.times); index += 1) {
|
|
781
|
+
paths = walkHandSteps(step.do, paths, depth + 1, context);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return dedupeHandPaths(paths);
|
|
786
|
+
}
|
|
787
|
+
function dedupeHandPaths(paths) {
|
|
788
|
+
const unique = new Map();
|
|
789
|
+
for (const path of paths) {
|
|
790
|
+
const key = `${path.state}:${path.delays.map((delay) => delay.id).join(',')}`;
|
|
791
|
+
if (!unique.has(key))
|
|
792
|
+
unique.set(key, cloneHandPath(path));
|
|
793
|
+
}
|
|
794
|
+
return [...unique.values()];
|
|
795
|
+
}
|
|
796
|
+
function handRelation(steps, depth, context) {
|
|
797
|
+
if (steps && typeof steps === 'object') {
|
|
798
|
+
const cached = context.relations.get(steps);
|
|
799
|
+
if (cached)
|
|
800
|
+
return cached;
|
|
801
|
+
}
|
|
802
|
+
const relation = {
|
|
803
|
+
id: context.nextRelationId++,
|
|
804
|
+
outputs: [new Set(), new Set(), new Set(), new Set()],
|
|
805
|
+
};
|
|
806
|
+
if (steps && typeof steps === 'object')
|
|
807
|
+
context.relations.set(steps, relation);
|
|
808
|
+
for (let state = 0; state < 4; state += 1) {
|
|
809
|
+
relation.outputs[state] = new Set(handUnitEnds(steps, [state], depth, context));
|
|
810
|
+
}
|
|
811
|
+
return relation;
|
|
812
|
+
}
|
|
813
|
+
function finishHandDelays(state, delays) {
|
|
814
|
+
if (delays.length === 0)
|
|
815
|
+
return [state];
|
|
816
|
+
const full = (1 << delays.length) - 1;
|
|
817
|
+
const seen = new Set();
|
|
818
|
+
const endings = new Set();
|
|
819
|
+
const visit = (mask, current) => {
|
|
820
|
+
const key = `${mask}:${current}`;
|
|
821
|
+
if (seen.has(key))
|
|
822
|
+
return;
|
|
823
|
+
seen.add(key);
|
|
824
|
+
if (mask === full) {
|
|
825
|
+
endings.add(current);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
delays.forEach((relation, index) => {
|
|
829
|
+
const bit = 1 << index;
|
|
830
|
+
if (mask & bit)
|
|
831
|
+
return;
|
|
832
|
+
for (const next of relation.outputs[current] ?? []) {
|
|
833
|
+
visit(mask | bit, next);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
visit(0, state);
|
|
838
|
+
return [...endings];
|
|
839
|
+
}
|
|
840
|
+
function handBit(hand) {
|
|
841
|
+
return hand === 'left' ? 1 : hand === 'right' ? 2 : 0;
|
|
842
|
+
}
|
|
843
|
+
function handSetToMask(hands) {
|
|
844
|
+
let mask = 0;
|
|
845
|
+
for (const hand of hands)
|
|
846
|
+
mask |= handBit(hand);
|
|
847
|
+
return mask;
|
|
848
|
+
}
|
|
849
|
+
function handMaskToSet(mask) {
|
|
850
|
+
const hands = new Set();
|
|
851
|
+
if (mask & 1)
|
|
852
|
+
hands.add('left');
|
|
853
|
+
if (mask & 2)
|
|
854
|
+
hands.add('right');
|
|
855
|
+
return hands;
|
|
856
|
+
}
|
|
857
|
+
function dedupeHandMasks(states) {
|
|
858
|
+
return [...new Set(states)];
|
|
859
|
+
}
|
|
860
|
+
function cloneHandPath(path) {
|
|
861
|
+
return { state: path.state, delays: [...path.delays] };
|
|
862
|
+
}
|
|
863
|
+
function handGraphExecutionMetrics(steps, depth = 0) {
|
|
864
|
+
const current = { steps: 0 };
|
|
865
|
+
const delayed = [];
|
|
866
|
+
if (!Array.isArray(steps) || depth > DEVICE_GRAPH_MAX_BRANCH_DEPTH) {
|
|
867
|
+
return [current];
|
|
868
|
+
}
|
|
869
|
+
for (const raw of steps) {
|
|
870
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
871
|
+
continue;
|
|
872
|
+
const step = object(raw);
|
|
873
|
+
const kind = String(step.step ?? '');
|
|
874
|
+
current.steps = Math.min(DEVICE_BUDGETS.maxStepsPerFire, current.steps + 1);
|
|
875
|
+
if (kind === 'delay') {
|
|
876
|
+
delayed.push(...handGraphExecutionMetrics(step.then, depth + 1));
|
|
877
|
+
}
|
|
878
|
+
else if (kind === 'sequence') {
|
|
879
|
+
const [body, ...children] = handGraphExecutionMetrics(step.do, depth + 1);
|
|
880
|
+
current.steps = cappedStepSum(current.steps, body.steps);
|
|
881
|
+
delayed.push(...children);
|
|
882
|
+
}
|
|
883
|
+
else if (kind === 'repeat' && Number.isInteger(step.times)) {
|
|
884
|
+
const times = Math.max(0, Number(step.times));
|
|
885
|
+
const [body, ...children] = handGraphExecutionMetrics(step.do, depth + 1);
|
|
886
|
+
current.steps = cappedStepSum(current.steps, times * (1 + body.steps));
|
|
887
|
+
delayed.push(...children);
|
|
888
|
+
}
|
|
889
|
+
else if (kind === 'branch') {
|
|
890
|
+
const condition = object(step.when);
|
|
891
|
+
const arms = condition.op === 'true'
|
|
892
|
+
? [step.then]
|
|
893
|
+
: condition.op === 'false'
|
|
894
|
+
? [step.else ?? []]
|
|
895
|
+
: [step.then, step.else ?? []];
|
|
896
|
+
const armMetrics = arms.map((arm) => handGraphExecutionMetrics(arm, depth + 1));
|
|
897
|
+
current.steps = cappedStepSum(current.steps, Math.max(0, ...armMetrics.map(([arm]) => arm.steps)));
|
|
898
|
+
for (const [, ...children] of armMetrics)
|
|
899
|
+
delayed.push(...children);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return [current, ...delayed];
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Maximum delay-queue width reachable from one execution unit. A queued delay
|
|
906
|
+
* occupies one slot until it fires; its continuation can replace that slot
|
|
907
|
+
* with every delay it schedules while unrelated siblings remain pending.
|
|
908
|
+
* Taking the widest reachable replacement forest catches nested overflow
|
|
909
|
+
* without rejecting a long chain that still occupies only one slot at a time.
|
|
910
|
+
*/
|
|
911
|
+
function handGraphMaxPendingDelays(steps, depth = 0) {
|
|
912
|
+
if (!Array.isArray(steps) || depth > DEVICE_GRAPH_MAX_BRANCH_DEPTH)
|
|
913
|
+
return 0;
|
|
914
|
+
let width = 0;
|
|
915
|
+
for (const raw of steps) {
|
|
916
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
917
|
+
continue;
|
|
918
|
+
const step = object(raw);
|
|
919
|
+
const kind = String(step.step ?? '');
|
|
920
|
+
if (kind === 'delay') {
|
|
921
|
+
width += Math.max(1, handGraphMaxPendingDelays(step.then, depth + 1));
|
|
922
|
+
}
|
|
923
|
+
else if (kind === 'sequence') {
|
|
924
|
+
width += handGraphMaxPendingDelays(step.do, depth + 1);
|
|
925
|
+
}
|
|
926
|
+
else if (kind === 'repeat' && Number.isInteger(step.times)) {
|
|
927
|
+
width += Math.max(0, Number(step.times)) *
|
|
928
|
+
handGraphMaxPendingDelays(step.do, depth + 1);
|
|
929
|
+
}
|
|
930
|
+
else if (kind === 'branch') {
|
|
931
|
+
const condition = object(step.when);
|
|
932
|
+
const arms = condition.op === 'true'
|
|
933
|
+
? [step.then]
|
|
934
|
+
: condition.op === 'false'
|
|
935
|
+
? [step.else ?? []]
|
|
936
|
+
: [step.then, step.else ?? []];
|
|
937
|
+
width += Math.max(0, ...arms.map((arm) => handGraphMaxPendingDelays(arm, depth + 1)));
|
|
938
|
+
}
|
|
939
|
+
if (width > DEVICE_BUDGETS.maxConcurrentDelays)
|
|
940
|
+
return width;
|
|
941
|
+
}
|
|
942
|
+
return width;
|
|
943
|
+
}
|
|
944
|
+
function cappedStepSum(left, right) {
|
|
945
|
+
return Math.min(DEVICE_BUDGETS.maxStepsPerFire, left + right);
|
|
946
|
+
}
|
|
947
|
+
function graphHasHandStep(steps) {
|
|
948
|
+
if (!Array.isArray(steps))
|
|
949
|
+
return false;
|
|
950
|
+
return steps.some((raw) => {
|
|
951
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
952
|
+
return false;
|
|
953
|
+
const step = object(raw);
|
|
954
|
+
if (step.step === 'reachCharacterHand' ||
|
|
955
|
+
step.step === 'releaseCharacterHand') {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
if (step.step === 'branch') {
|
|
959
|
+
return graphHasHandStep(step.then) || graphHasHandStep(step.else);
|
|
960
|
+
}
|
|
961
|
+
if (step.step === 'delay')
|
|
962
|
+
return graphHasHandStep(step.then);
|
|
963
|
+
if (step.step === 'sequence' || step.step === 'repeat') {
|
|
964
|
+
return graphHasHandStep(step.do);
|
|
965
|
+
}
|
|
966
|
+
return false;
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
const DEVICE_VFX_TYPES = new Set(['particles', 'glow']);
|
|
970
|
+
const DEVICE_VFX_SHAPES = new Set(['cone', 'sphere', 'disc']);
|
|
971
|
+
/**
|
|
972
|
+
* The host's `vfxSpec` (`validate.ts:283-296`) in full. `effects` was entirely
|
|
973
|
+
* unread offline, so a `setVfx` step could name an effect and the effect itself
|
|
974
|
+
* could be shapeless and still validate.
|
|
975
|
+
*/
|
|
976
|
+
function validateDeviceVfxSpec(raw, sockets, path, errors) {
|
|
977
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
978
|
+
errors.push(`${path}: effect must be an object`);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const spec = object(raw);
|
|
982
|
+
if (!DEVICE_VFX_TYPES.has(String(spec.type)))
|
|
983
|
+
errors.push(`${path}: unsupported effect type "${String(spec.type)}" (one of ${[...DEVICE_VFX_TYPES].join('|')})`);
|
|
984
|
+
if (!DEVICE_VFX_SHAPES.has(String(spec.shape)))
|
|
985
|
+
errors.push(`${path}: unsupported effect shape "${String(spec.shape)}" (one of ${[...DEVICE_VFX_SHAPES].join('|')})`);
|
|
986
|
+
if (spec.at !== undefined) {
|
|
987
|
+
if (!isNonEmptyName(spec.at))
|
|
988
|
+
errors.push(`${path}.at: must be a non-empty name`);
|
|
989
|
+
else if (!sockets.has(String(spec.at)))
|
|
990
|
+
errors.push(`${path}: unknown socket "${String(spec.at)}"`);
|
|
991
|
+
}
|
|
992
|
+
const bounded = [
|
|
993
|
+
['radius', 0, 10],
|
|
994
|
+
['rate', 0, 2_000],
|
|
995
|
+
['spread', 0, 20],
|
|
996
|
+
];
|
|
997
|
+
for (const [field, min, max] of bounded) {
|
|
998
|
+
const value = spec[field];
|
|
999
|
+
if (value === undefined)
|
|
1000
|
+
continue;
|
|
1001
|
+
if (!isFiniteNumber(value) || value < min || value > max)
|
|
1002
|
+
errors.push(`${path}.${field}: must be between ${min} and ${max}`);
|
|
1003
|
+
}
|
|
1004
|
+
if (spec.lifetime !== undefined) {
|
|
1005
|
+
if (!isFiniteNumber(spec.lifetime) ||
|
|
1006
|
+
spec.lifetime <= 0 ||
|
|
1007
|
+
spec.lifetime > 30) {
|
|
1008
|
+
errors.push(`${path}.lifetime: must be > 0 and <= 30`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (spec.velocity !== undefined && !isVec3(spec.velocity))
|
|
1012
|
+
errors.push(`${path}.velocity: must be [x, y, z]`);
|
|
1013
|
+
if (spec.size !== undefined) {
|
|
1014
|
+
if (!Array.isArray(spec.size) ||
|
|
1015
|
+
spec.size.length !== 2 ||
|
|
1016
|
+
spec.size.some((value) => !isFiniteNumber(value) || value < 0 || value > 10)) {
|
|
1017
|
+
errors.push(`${path}.size: must be two numbers between 0 and 10`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (spec.color !== undefined && !/^#[0-9a-f]{6}$/i.test(String(spec.color)))
|
|
1021
|
+
errors.push(`${path}.color: must be #RRGGBB`);
|
|
1022
|
+
if (spec.opacity !== undefined) {
|
|
1023
|
+
if (!Array.isArray(spec.opacity) ||
|
|
1024
|
+
spec.opacity.length !== 2 ||
|
|
1025
|
+
spec.opacity.some((value) => !isFiniteNumber(value) || value < 0 || value > 1)) {
|
|
1026
|
+
errors.push(`${path}.opacity: must be two numbers between 0 and 1`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (spec.gravity !== undefined && typeof spec.gravity !== 'boolean')
|
|
1030
|
+
errors.push(`${path}.gravity: must be boolean`);
|
|
1031
|
+
}
|
|
1032
|
+
function readDefinitionPath(input) {
|
|
1033
|
+
const target = (0, node_path_1.resolve)(input);
|
|
1034
|
+
if (target.endsWith('.json')) {
|
|
1035
|
+
return { directory: (0, node_path_1.dirname)(target), path: target };
|
|
1036
|
+
}
|
|
1037
|
+
return { directory: target, path: (0, node_path_1.join)(target, exports.DEVICE_FILENAME) };
|
|
1038
|
+
}
|
|
1039
|
+
function glbJson(file) {
|
|
1040
|
+
const bytes = (0, node_fs_1.readFileSync)(file);
|
|
1041
|
+
if (bytes.length < 20 || bytes.readUInt32LE(0) !== 0x46546c67) {
|
|
1042
|
+
throw new Error(`${(0, node_path_1.basename)(file)} is not a binary glTF (.glb)`);
|
|
1043
|
+
}
|
|
1044
|
+
if (bytes.readUInt32LE(4) !== 2) {
|
|
1045
|
+
throw new Error(`${(0, node_path_1.basename)(file)} must use glTF 2`);
|
|
1046
|
+
}
|
|
1047
|
+
const declaredLength = bytes.readUInt32LE(8);
|
|
1048
|
+
if (declaredLength !== bytes.length) {
|
|
1049
|
+
throw new Error(`${(0, node_path_1.basename)(file)} length header is ${declaredLength}, actual ${bytes.length}`);
|
|
1050
|
+
}
|
|
1051
|
+
const jsonLength = bytes.readUInt32LE(12);
|
|
1052
|
+
if (bytes.readUInt32LE(16) !== 0x4e4f534a || 20 + jsonLength > bytes.length) {
|
|
1053
|
+
throw new Error(`${(0, node_path_1.basename)(file)} has no valid JSON chunk`);
|
|
1054
|
+
}
|
|
1055
|
+
return JSON.parse(bytes.subarray(20, 20 + jsonLength).toString('utf8'));
|
|
1056
|
+
}
|
|
1057
|
+
function readDeviceGlbNodeNames(file) {
|
|
1058
|
+
const parsed = glbJson(file);
|
|
1059
|
+
return [
|
|
1060
|
+
...new Set((Array.isArray(parsed.nodes) ? parsed.nodes : [])
|
|
1061
|
+
.map((node) => String(object(node).name ?? '').trim())
|
|
1062
|
+
.filter(Boolean)),
|
|
1063
|
+
].sort();
|
|
1064
|
+
}
|
|
1065
|
+
function stateDefault(spec) {
|
|
1066
|
+
if (scalar(spec.default))
|
|
1067
|
+
return spec.default;
|
|
1068
|
+
const type = String(spec.type ?? '');
|
|
1069
|
+
if (type === 'boolean')
|
|
1070
|
+
return false;
|
|
1071
|
+
if (type === 'enum')
|
|
1072
|
+
return String((Array.isArray(spec.values) ? spec.values : [])[0] ?? '');
|
|
1073
|
+
if (type === 'string')
|
|
1074
|
+
return '';
|
|
1075
|
+
return typeof spec.min === 'number' ? spec.min : 0;
|
|
1076
|
+
}
|
|
1077
|
+
function coerce(spec, value) {
|
|
1078
|
+
const type = String(spec.type ?? '');
|
|
1079
|
+
if (type === 'boolean')
|
|
1080
|
+
return value === true;
|
|
1081
|
+
if (type === 'string') {
|
|
1082
|
+
const text = String(value ?? '');
|
|
1083
|
+
return typeof spec.maxLen === 'number' ? text.slice(0, spec.maxLen) : text;
|
|
1084
|
+
}
|
|
1085
|
+
if (type === 'enum') {
|
|
1086
|
+
const next = String(value ?? '');
|
|
1087
|
+
const values = Array.isArray(spec.values) ? spec.values.map(String) : [];
|
|
1088
|
+
return values.includes(next) ? next : String(values[0] ?? '');
|
|
1089
|
+
}
|
|
1090
|
+
let number = Number(value);
|
|
1091
|
+
if (!Number.isFinite(number))
|
|
1092
|
+
number = Number(stateDefault(spec));
|
|
1093
|
+
if (type === 'integer')
|
|
1094
|
+
number = Math.round(number);
|
|
1095
|
+
if (typeof spec.min === 'number')
|
|
1096
|
+
number = Math.max(spec.min, number);
|
|
1097
|
+
if (typeof spec.max === 'number')
|
|
1098
|
+
number = Math.min(spec.max, number);
|
|
1099
|
+
return number;
|
|
1100
|
+
}
|
|
1101
|
+
function initialState(definition) {
|
|
1102
|
+
return Object.fromEntries(Object.entries(object(definition.state)).map(([key, raw]) => [
|
|
1103
|
+
key,
|
|
1104
|
+
stateDefault(object(raw)),
|
|
1105
|
+
]));
|
|
1106
|
+
}
|
|
1107
|
+
function expr(value, state) {
|
|
1108
|
+
const node = object(value);
|
|
1109
|
+
if (node.kind === 'lit' && scalar(node.value))
|
|
1110
|
+
return node.value;
|
|
1111
|
+
if (node.kind === 'state')
|
|
1112
|
+
return state[String(node.key)] ?? 0;
|
|
1113
|
+
const args = Array.isArray(node.args)
|
|
1114
|
+
? node.args.map((part) => Number(expr(part, state)))
|
|
1115
|
+
: [];
|
|
1116
|
+
if (node.kind === 'add')
|
|
1117
|
+
return args.reduce((a, b) => a + b, 0);
|
|
1118
|
+
if (node.kind === 'sub')
|
|
1119
|
+
return args.slice(1).reduce((a, b) => a - b, args[0] ?? 0);
|
|
1120
|
+
if (node.kind === 'mul')
|
|
1121
|
+
return args.reduce((a, b) => a * b, 1);
|
|
1122
|
+
if (node.kind === 'min')
|
|
1123
|
+
return Math.min(...args);
|
|
1124
|
+
if (node.kind === 'max')
|
|
1125
|
+
return Math.max(...args);
|
|
1126
|
+
return 0;
|
|
1127
|
+
}
|
|
1128
|
+
function condition(value, state) {
|
|
1129
|
+
const node = object(value);
|
|
1130
|
+
if (node.op === 'true')
|
|
1131
|
+
return true;
|
|
1132
|
+
if (node.op === 'false')
|
|
1133
|
+
return false;
|
|
1134
|
+
if (node.op === 'not')
|
|
1135
|
+
return !condition(node.of, state);
|
|
1136
|
+
const list = Array.isArray(node.of) ? node.of : [];
|
|
1137
|
+
if (node.op === 'and')
|
|
1138
|
+
return list.every((part) => condition(part, state));
|
|
1139
|
+
if (node.op === 'or')
|
|
1140
|
+
return list.some((part) => condition(part, state));
|
|
1141
|
+
const left = expr(node.left, state);
|
|
1142
|
+
const right = expr(node.right, state);
|
|
1143
|
+
if (node.op === 'eq')
|
|
1144
|
+
return left === right;
|
|
1145
|
+
if (node.op === 'ne')
|
|
1146
|
+
return left !== right;
|
|
1147
|
+
if (node.op === 'gt')
|
|
1148
|
+
return left > right;
|
|
1149
|
+
if (node.op === 'lt')
|
|
1150
|
+
return left < right;
|
|
1151
|
+
if (node.op === 'gte')
|
|
1152
|
+
return left >= right;
|
|
1153
|
+
if (node.op === 'lte')
|
|
1154
|
+
return left <= right;
|
|
1155
|
+
if (node.op === 'inRange') {
|
|
1156
|
+
const number = Number(expr(node.value, state));
|
|
1157
|
+
return (number >= Number(expr(node.min, state)) &&
|
|
1158
|
+
number <= Number(expr(node.max, state)));
|
|
1159
|
+
}
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
function applySteps(steps, definition, state, budget) {
|
|
1163
|
+
if (!Array.isArray(steps))
|
|
1164
|
+
return;
|
|
1165
|
+
for (const raw of steps) {
|
|
1166
|
+
if (--budget.remaining < 0)
|
|
1167
|
+
throw new Error('behavior graph exceeded 2048 simulated steps');
|
|
1168
|
+
const step = object(raw);
|
|
1169
|
+
const kind = String(step.step ?? '');
|
|
1170
|
+
if (kind === 'setState') {
|
|
1171
|
+
const key = String(step.key ?? '');
|
|
1172
|
+
const spec = object(object(definition.state)[key]);
|
|
1173
|
+
if (!Object.keys(spec).length)
|
|
1174
|
+
continue;
|
|
1175
|
+
const value = step.value !== undefined
|
|
1176
|
+
? expr(step.value, state)
|
|
1177
|
+
: Number(state[key] ?? 0) + Number(expr(step.delta, state));
|
|
1178
|
+
state[key] = coerce(spec, value);
|
|
1179
|
+
}
|
|
1180
|
+
else if (kind === 'toggleState') {
|
|
1181
|
+
const key = String(step.key ?? '');
|
|
1182
|
+
state[key] = !Boolean(state[key]);
|
|
1183
|
+
}
|
|
1184
|
+
else if (kind === 'branch') {
|
|
1185
|
+
applySteps(condition(step.when, state) ? step.then : step.else, definition, state, budget);
|
|
1186
|
+
}
|
|
1187
|
+
else if (kind === 'sequence') {
|
|
1188
|
+
applySteps(step.do, definition, state, budget);
|
|
1189
|
+
}
|
|
1190
|
+
else if (kind === 'repeat') {
|
|
1191
|
+
const times = Math.max(0, Math.min(64, Number(step.times) || 0));
|
|
1192
|
+
for (let index = 0; index < times; index += 1) {
|
|
1193
|
+
applySteps(step.do, definition, state, budget);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
else if (kind === 'delay') {
|
|
1197
|
+
applySteps(step.then, definition, state, budget);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function invoke(definition, state, interaction, value) {
|
|
1202
|
+
const control = object(interaction.control);
|
|
1203
|
+
if (control.stateKey && value !== undefined) {
|
|
1204
|
+
const key = String(control.stateKey);
|
|
1205
|
+
state[key] = coerce(object(object(definition.state)[key]), value);
|
|
1206
|
+
}
|
|
1207
|
+
const triggers = Array.isArray(object(definition.graph).triggers)
|
|
1208
|
+
? object(definition.graph).triggers
|
|
1209
|
+
: [];
|
|
1210
|
+
for (const raw of triggers) {
|
|
1211
|
+
const trigger = object(raw);
|
|
1212
|
+
if (trigger.on === 'interact' && trigger.interaction === interaction.id) {
|
|
1213
|
+
applySteps(trigger.do, definition, state, { remaining: 2048 });
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
function exerciseValue(interaction, state) {
|
|
1218
|
+
const control = object(interaction.control);
|
|
1219
|
+
if (control.kind === 'number')
|
|
1220
|
+
return Number(control.max ?? control.min ?? 1);
|
|
1221
|
+
if (control.kind === 'choice') {
|
|
1222
|
+
const options = Array.isArray(control.options)
|
|
1223
|
+
? control.options.map(object)
|
|
1224
|
+
: [];
|
|
1225
|
+
const current = state[String(control.stateKey)];
|
|
1226
|
+
return String(options.find((option) => !option.disabled && option.value !== current)
|
|
1227
|
+
?.value ??
|
|
1228
|
+
options.find((option) => !option.disabled)?.value ??
|
|
1229
|
+
'');
|
|
1230
|
+
}
|
|
1231
|
+
if (control.kind === 'text') {
|
|
1232
|
+
return 'https://www.youtube.com/watch?v=M7lc1UVf-VE';
|
|
1233
|
+
}
|
|
1234
|
+
return undefined;
|
|
1235
|
+
}
|
|
1236
|
+
/** Viewer-local declarations never become room actions or state writes. */
|
|
1237
|
+
function deviceWebPanelInteractionError(definition, interaction) {
|
|
1238
|
+
const plainRecord = (value) => isRecord(value) ? value : null;
|
|
1239
|
+
if (interaction.presentation === undefined)
|
|
1240
|
+
return null;
|
|
1241
|
+
const presentation = plainRecord(interaction.presentation);
|
|
1242
|
+
if (!presentation ||
|
|
1243
|
+
presentation.kind !== 'webPanel' ||
|
|
1244
|
+
Object.keys(presentation).some((key) => key !== 'kind' && key !== 'app') ||
|
|
1245
|
+
typeof presentation.app !== 'string' ||
|
|
1246
|
+
!presentation.app.trim() ||
|
|
1247
|
+
presentation.app.length > 64)
|
|
1248
|
+
return 'presentation must be {kind:webPanel,app:<declared app id>}';
|
|
1249
|
+
if (interaction.action !== undefined || interaction.control !== undefined)
|
|
1250
|
+
return 'webPanel presentation cannot include action or control';
|
|
1251
|
+
if (interaction.surface === 'internal')
|
|
1252
|
+
return 'webPanel presentation cannot use an internal interaction';
|
|
1253
|
+
const capabilities = definition.capabilities;
|
|
1254
|
+
if (!Array.isArray(capabilities) || !capabilities.includes('app.web.panel'))
|
|
1255
|
+
return 'webPanel presentation requires capability app.web.panel';
|
|
1256
|
+
const apps = plainRecord(definition.apps);
|
|
1257
|
+
const app = apps && Object.prototype.hasOwnProperty.call(apps, presentation.app)
|
|
1258
|
+
? plainRecord(apps[presentation.app])
|
|
1259
|
+
: null;
|
|
1260
|
+
if (!app || (app.type !== 'remoteWeb' && app.type !== 'localWeb'))
|
|
1261
|
+
return 'webPanel presentation must target a declared remoteWeb or localWeb app';
|
|
1262
|
+
if (!capabilities.includes(app.type === 'remoteWeb' ? 'app.web.remote' : 'app.web.local'))
|
|
1263
|
+
return `webPanel app requires capability ${app.type === 'remoteWeb' ? 'app.web.remote' : 'app.web.local'}`;
|
|
1264
|
+
if (typeof app.entry !== 'string' ||
|
|
1265
|
+
!app.entry.trim() ||
|
|
1266
|
+
app.entry.length > 2048)
|
|
1267
|
+
return 'webPanel app entry must be a non-empty string of at most 2048 characters';
|
|
1268
|
+
if (app.type === 'localWeb') {
|
|
1269
|
+
if (app.allowedOrigins !== undefined)
|
|
1270
|
+
return 'webPanel localWeb app cannot declare allowedOrigins';
|
|
1271
|
+
try {
|
|
1272
|
+
const url = new URL(app.entry, 'https://helix.invalid');
|
|
1273
|
+
if (!app.entry.startsWith('/device-apps/') ||
|
|
1274
|
+
app.entry.includes('\\') ||
|
|
1275
|
+
app.entry.split(/[/?#]/).includes('..') ||
|
|
1276
|
+
url.origin !== 'https://helix.invalid' ||
|
|
1277
|
+
!/^\/device-apps\/[A-Za-z0-9][A-Za-z0-9/_.-]*$/.test(url.pathname) ||
|
|
1278
|
+
decodeURIComponent(url.pathname) !== url.pathname)
|
|
1279
|
+
return 'webPanel localWeb entry must use a safe /device-apps/ path';
|
|
1280
|
+
}
|
|
1281
|
+
catch {
|
|
1282
|
+
return 'webPanel localWeb entry must use a safe /device-apps/ path';
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
try {
|
|
1287
|
+
const url = new URL(app.entry);
|
|
1288
|
+
const origins = app.allowedOrigins;
|
|
1289
|
+
if (url.protocol !== 'https:' ||
|
|
1290
|
+
url.username ||
|
|
1291
|
+
url.password ||
|
|
1292
|
+
!Array.isArray(origins) ||
|
|
1293
|
+
origins.length < 1 ||
|
|
1294
|
+
origins.length > 16 ||
|
|
1295
|
+
!origins.every((origin) => {
|
|
1296
|
+
if (typeof origin !== 'string')
|
|
1297
|
+
return false;
|
|
1298
|
+
const allowed = new URL(origin);
|
|
1299
|
+
return allowed.protocol === 'https:' && allowed.origin === origin;
|
|
1300
|
+
}) ||
|
|
1301
|
+
!origins.includes(url.origin))
|
|
1302
|
+
return 'webPanel remoteWeb entry must use HTTPS and a declared allowedOrigins origin';
|
|
1303
|
+
}
|
|
1304
|
+
catch {
|
|
1305
|
+
return 'webPanel remoteWeb entry must use HTTPS and a declared allowedOrigins origin';
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return null;
|
|
1309
|
+
}
|
|
1310
|
+
function validateDeviceProject(input) {
|
|
1311
|
+
const target = readDefinitionPath(input);
|
|
1312
|
+
const errors = [];
|
|
1313
|
+
const warnings = [];
|
|
1314
|
+
let definition = {};
|
|
1315
|
+
if (!(0, node_fs_1.existsSync)(target.path)) {
|
|
1316
|
+
return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
directory: target.directory,
|
|
1319
|
+
definitionPath: target.path,
|
|
1320
|
+
modelPath: null,
|
|
1321
|
+
nodeNames: [],
|
|
1322
|
+
errors: [`Missing ${target.path}`],
|
|
1323
|
+
warnings,
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
definition = object(JSON.parse((0, node_fs_1.readFileSync)(target.path, 'utf8')));
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
1331
|
+
}
|
|
1332
|
+
const actionAstError = (0, deviceActionValidation_1.deviceActionAstBudgetError)(definition);
|
|
1333
|
+
if (actionAstError)
|
|
1334
|
+
errors.push(`interactions: ${actionAstError}`);
|
|
1335
|
+
if (definition.format !== 'helix.device' || definition.formatVersion !== 1) {
|
|
1336
|
+
errors.push('format must be helix.device/1');
|
|
1337
|
+
}
|
|
1338
|
+
if (!/^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/.test(String(definition.id ?? ''))) {
|
|
1339
|
+
errors.push('id must be a 3-50 character lowercase slug');
|
|
1340
|
+
}
|
|
1341
|
+
if (!/^\d+\.\d+\.\d+$/.test(String(definition.version ?? ''))) {
|
|
1342
|
+
errors.push('version must be semver (for example 1.0.0)');
|
|
1343
|
+
}
|
|
1344
|
+
// The host's schema marks these four non-optional, so a package that omits
|
|
1345
|
+
// one is rejected at load. Reading them through `object()` alone turned a
|
|
1346
|
+
// missing block into an empty one and hid the rejection.
|
|
1347
|
+
for (const key of ['state', 'interactions', 'components', 'graph']) {
|
|
1348
|
+
if (definition[key] === undefined || definition[key] === null)
|
|
1349
|
+
errors.push(`${key} is required`);
|
|
1350
|
+
}
|
|
1351
|
+
const title = String(definition.title ?? '');
|
|
1352
|
+
if (!title.trim())
|
|
1353
|
+
errors.push('title is required');
|
|
1354
|
+
else if (title.length > 80)
|
|
1355
|
+
errors.push('title must be 80 characters or fewer');
|
|
1356
|
+
if (definition.description !== undefined) {
|
|
1357
|
+
if (typeof definition.description !== 'string')
|
|
1358
|
+
errors.push('description must be a string');
|
|
1359
|
+
else if (definition.description.length > 400)
|
|
1360
|
+
errors.push('description must be 400 characters or fewer');
|
|
1361
|
+
}
|
|
1362
|
+
if (definition.stateVersion !== undefined &&
|
|
1363
|
+
(!Number.isInteger(definition.stateVersion) ||
|
|
1364
|
+
Number(definition.stateVersion) < 1)) {
|
|
1365
|
+
errors.push('stateVersion must be a positive integer');
|
|
1366
|
+
}
|
|
1367
|
+
if (definition.script !== null)
|
|
1368
|
+
errors.push('script must be null; Devices are declarative');
|
|
1369
|
+
const authorityModes = [
|
|
1370
|
+
'presentationOnly',
|
|
1371
|
+
'clientPredicted',
|
|
1372
|
+
'clientAuthoritative',
|
|
1373
|
+
'serverValidated',
|
|
1374
|
+
'serverAuthoritative',
|
|
1375
|
+
];
|
|
1376
|
+
if (!authorityModes.includes(String(definition.authority))) {
|
|
1377
|
+
errors.push(`authority "${String(definition.authority)}" is unsupported`);
|
|
1378
|
+
}
|
|
1379
|
+
if (!['tiny', 'room', 'nearby', 'building', 'world'].includes(String(definition.activationPreset))) {
|
|
1380
|
+
errors.push(`activationPreset "${String(definition.activationPreset)}" is unsupported`);
|
|
1381
|
+
}
|
|
1382
|
+
const model = object(definition.model);
|
|
1383
|
+
let modelPath = null;
|
|
1384
|
+
let nodeNames = [];
|
|
1385
|
+
if (model.kind === 'procedural') {
|
|
1386
|
+
const parts = object(model.parts);
|
|
1387
|
+
if (Object.keys(parts).length === 0) {
|
|
1388
|
+
errors.push('model.parts must define at least one procedural part');
|
|
1389
|
+
}
|
|
1390
|
+
if (Object.keys(parts).length > 64) {
|
|
1391
|
+
errors.push('model.parts exceeds the 64-part Device budget');
|
|
1392
|
+
}
|
|
1393
|
+
nodeNames = Object.keys(parts);
|
|
1394
|
+
for (const [name, rawPart] of Object.entries(parts)) {
|
|
1395
|
+
const part = object(rawPart);
|
|
1396
|
+
const shape = object(part.shape);
|
|
1397
|
+
const partPath = `model.parts.${name}`;
|
|
1398
|
+
if (!name.trim())
|
|
1399
|
+
errors.push('procedural part names must not be empty');
|
|
1400
|
+
const kind = String(shape.kind);
|
|
1401
|
+
if (!['box', 'cylinder', 'sphere'].includes(kind)) {
|
|
1402
|
+
errors.push(`${partPath}.shape.kind is unsupported`);
|
|
1403
|
+
}
|
|
1404
|
+
else if (kind === 'box') {
|
|
1405
|
+
if (!isNumberTriple(shape.size)) {
|
|
1406
|
+
errors.push(`${partPath}.shape.size must be three numbers`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
else if (kind === 'cylinder') {
|
|
1410
|
+
for (const field of ['radiusTop', 'radiusBottom', 'height']) {
|
|
1411
|
+
if (typeof shape[field] !== 'number' || !(Number(shape[field]) > 0)) {
|
|
1412
|
+
errors.push(`${partPath}.shape.${field} must be a positive number`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (shape.segments !== undefined &&
|
|
1416
|
+
(!Number.isInteger(shape.segments) ||
|
|
1417
|
+
Number(shape.segments) < 3 ||
|
|
1418
|
+
Number(shape.segments) > 64)) {
|
|
1419
|
+
errors.push(`${partPath}.shape.segments must be an integer 3..64`);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
else if (typeof shape.radius !== 'number' || !(Number(shape.radius) > 0)) {
|
|
1423
|
+
errors.push(`${partPath}.shape.radius must be a positive number`);
|
|
1424
|
+
}
|
|
1425
|
+
for (const field of ['position', 'rotationDeg']) {
|
|
1426
|
+
if (part[field] !== undefined && !isNumberTriple(part[field])) {
|
|
1427
|
+
errors.push(`${partPath}.${field} must be three numbers`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (part.pivot !== undefined && part.pivot !== 'bottom') {
|
|
1431
|
+
errors.push(`${partPath}.pivot must be bottom`);
|
|
1432
|
+
}
|
|
1433
|
+
if (part.color !== undefined && !Number.isInteger(part.color)) {
|
|
1434
|
+
errors.push(`${partPath}.color must be an integer`);
|
|
1435
|
+
}
|
|
1436
|
+
for (const field of ['metalness', 'roughness', 'emissive']) {
|
|
1437
|
+
const value = part[field];
|
|
1438
|
+
if (value !== undefined &&
|
|
1439
|
+
(typeof value !== 'number' || value < 0 || value > 1)) {
|
|
1440
|
+
errors.push(`${partPath}.${field} must be between 0 and 1`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
else if (model.kind !== 'glb' || !String(model.url ?? '').trim()) {
|
|
1446
|
+
errors.push('model.kind must be procedural or glb with a url');
|
|
1447
|
+
}
|
|
1448
|
+
else if (/^https?:\/\//.test(String(model.url))) {
|
|
1449
|
+
warnings.push('remote model URL cannot be byte-verified locally; prefer a project GLB');
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
modelPath = (0, node_path_1.resolve)(target.directory, String(model.url));
|
|
1453
|
+
if (!(0, node_fs_1.existsSync)(modelPath))
|
|
1454
|
+
errors.push(`model GLB does not exist: ${modelPath}`);
|
|
1455
|
+
else {
|
|
1456
|
+
try {
|
|
1457
|
+
nodeNames = readDeviceGlbNodeNames(modelPath);
|
|
1458
|
+
}
|
|
1459
|
+
catch (error) {
|
|
1460
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
const nodes = new Set(nodeNames);
|
|
1465
|
+
for (const [semantic, authored] of Object.entries(object(model.nodeBindings))) {
|
|
1466
|
+
if (nodeNames.length > 0 && !nodes.has(String(authored))) {
|
|
1467
|
+
errors.push(`model.nodeBindings.${semantic} references missing GLB node "${String(authored)}"`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
const state = object(definition.state);
|
|
1471
|
+
const stateKeys = Object.keys(state);
|
|
1472
|
+
if (stateKeys.length > DEVICE_BUDGETS.maxStateVars) {
|
|
1473
|
+
errors.push(`state: ${stateKeys.length} vars exceeds budget ${DEVICE_BUDGETS.maxStateVars}`);
|
|
1474
|
+
}
|
|
1475
|
+
const persistentCount = stateKeys.filter((key) => object(state[key]).persist === true).length;
|
|
1476
|
+
if (persistentCount > DEVICE_BUDGETS.maxPersistentVars) {
|
|
1477
|
+
errors.push(`state: ${persistentCount} persistent vars exceeds budget ${DEVICE_BUDGETS.maxPersistentVars}`);
|
|
1478
|
+
}
|
|
1479
|
+
for (const [key, raw] of Object.entries(state)) {
|
|
1480
|
+
const spec = object(raw);
|
|
1481
|
+
if (!['boolean', 'integer', 'float', 'string', 'enum'].includes(String(spec.type))) {
|
|
1482
|
+
errors.push(`state.${key}.type "${String(spec.type)}" is unsupported`);
|
|
1483
|
+
}
|
|
1484
|
+
if (typeof spec.min === 'number' &&
|
|
1485
|
+
typeof spec.max === 'number' &&
|
|
1486
|
+
spec.max < spec.min) {
|
|
1487
|
+
errors.push(`state.${key} has min > max`);
|
|
1488
|
+
}
|
|
1489
|
+
if (spec.type === 'enum' &&
|
|
1490
|
+
(!Array.isArray(spec.values) || spec.values.length === 0)) {
|
|
1491
|
+
errors.push(`state.${key} enum requires values`);
|
|
1492
|
+
}
|
|
1493
|
+
if (Array.isArray(spec.values) &&
|
|
1494
|
+
spec.values.length > DEVICE_BUDGETS.maxEnumValues) {
|
|
1495
|
+
errors.push(`state.${key}: ${spec.values.length} enum values exceeds budget ${DEVICE_BUDGETS.maxEnumValues}`);
|
|
1496
|
+
}
|
|
1497
|
+
if (spec.maxLen !== undefined) {
|
|
1498
|
+
// The room IR accepts 1..1024 and clips writes to it. Anything wider
|
|
1499
|
+
// validates here and is then refused when a Home compiles its room
|
|
1500
|
+
// config — which fails every Device in that Home, not just this one.
|
|
1501
|
+
if (!Number.isInteger(spec.maxLen) ||
|
|
1502
|
+
Number(spec.maxLen) < 1 ||
|
|
1503
|
+
Number(spec.maxLen) > 1024) {
|
|
1504
|
+
errors.push(`state.${key} maxLen must be 1..1024`);
|
|
1505
|
+
}
|
|
1506
|
+
if (!['string', 'enum'].includes(String(spec.type))) {
|
|
1507
|
+
errors.push(`state.${key} maxLen applies to string state only`);
|
|
1508
|
+
}
|
|
1509
|
+
if (typeof spec.default === 'string' &&
|
|
1510
|
+
spec.default.length > Number(spec.maxLen)) {
|
|
1511
|
+
errors.push(`state.${key} default is longer than maxLen`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
const interactions = Array.isArray(definition.interactions)
|
|
1516
|
+
? definition.interactions.map(object)
|
|
1517
|
+
: [];
|
|
1518
|
+
if (interactions.length > DEVICE_BUDGETS.maxInteractions)
|
|
1519
|
+
errors.push(`interactions: ${interactions.length} exceeds budget ${DEVICE_BUDGETS.maxInteractions}`);
|
|
1520
|
+
const interactionIds = new Set();
|
|
1521
|
+
for (const interaction of interactions) {
|
|
1522
|
+
const presentationError = deviceWebPanelInteractionError(definition, interaction);
|
|
1523
|
+
if (presentationError)
|
|
1524
|
+
errors.push(`interaction ${String(interaction.id)}: ${presentationError}`);
|
|
1525
|
+
const id = typeof interaction.id === 'string' ? interaction.id : '';
|
|
1526
|
+
if (!id)
|
|
1527
|
+
errors.push('interaction id must be a non-empty string');
|
|
1528
|
+
else if (id.length > 48)
|
|
1529
|
+
errors.push(`interaction id "${id}" must be 48 characters or fewer`);
|
|
1530
|
+
else if (interactionIds.has(id))
|
|
1531
|
+
errors.push(`interactions: duplicate id "${id}"`);
|
|
1532
|
+
interactionIds.add(id);
|
|
1533
|
+
// The host's schema makes `label` required and bounds it to 40 characters.
|
|
1534
|
+
// Every interaction the CLI's own emitter templates write already carries
|
|
1535
|
+
// one, which is why only hand-authored packages ever met this rejection.
|
|
1536
|
+
if (typeof interaction.label !== 'string' || !interaction.label.length)
|
|
1537
|
+
errors.push(`interaction ${id} requires a label`);
|
|
1538
|
+
else if (interaction.label.length > 40)
|
|
1539
|
+
errors.push(`interaction ${id} label must be 40 characters or fewer`);
|
|
1540
|
+
if (interaction.maxDistanceM !== undefined) {
|
|
1541
|
+
if (!isFiniteNumber(interaction.maxDistanceM) ||
|
|
1542
|
+
interaction.maxDistanceM <= 0 ||
|
|
1543
|
+
interaction.maxDistanceM > 20) {
|
|
1544
|
+
errors.push(`interaction ${id} maxDistanceM must be > 0 and <= 20`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (interaction.surface !== undefined &&
|
|
1548
|
+
!['menu', 'internal'].includes(String(interaction.surface))) {
|
|
1549
|
+
errors.push(`interaction ${id} has unsupported surface "${String(interaction.surface)}"`);
|
|
1550
|
+
}
|
|
1551
|
+
// The host's schema makes `verb` REQUIRED, so an absent verb is rejected
|
|
1552
|
+
// there exactly as an unknown one is; matching that here is the whole point.
|
|
1553
|
+
if (interaction.verb === undefined)
|
|
1554
|
+
errors.push(`interaction ${id} requires a verb (one of ${exports.DEVICE_INTERACTION_VERB_LIST})`);
|
|
1555
|
+
else if (typeof interaction.verb !== 'string' ||
|
|
1556
|
+
!INTERACTION_VERBS.has(interaction.verb))
|
|
1557
|
+
errors.push(`interaction ${id} verb "${String(interaction.verb)}" is unsupported; expected one of ${exports.DEVICE_INTERACTION_VERB_LIST}`);
|
|
1558
|
+
// The host's `control` union is discriminated on `kind` (`validate.ts:385-420`).
|
|
1559
|
+
// Guarding on `control.kind` skipped the whole block when the key was absent,
|
|
1560
|
+
// so a control with no `kind` — or none of the three kinds — passed offline
|
|
1561
|
+
// and was rejected at load with `interactions.N.control: Invalid input`.
|
|
1562
|
+
const control = object(interaction.control);
|
|
1563
|
+
if (interaction.control !== undefined && !isRecord(interaction.control)) {
|
|
1564
|
+
errors.push(`interaction ${id} control must be an object`);
|
|
1565
|
+
}
|
|
1566
|
+
else if (interaction.control !== undefined) {
|
|
1567
|
+
const key = String(control.stateKey ?? '');
|
|
1568
|
+
const spec = object(state[key]);
|
|
1569
|
+
if (!Object.keys(spec).length)
|
|
1570
|
+
errors.push(`interaction ${id} references unknown state "${key}"`);
|
|
1571
|
+
if (control.kind === 'number') {
|
|
1572
|
+
if (!['float', 'integer'].includes(String(spec.type)))
|
|
1573
|
+
errors.push(`interaction ${id} number control must target numeric state`);
|
|
1574
|
+
const min = Number(control.min ?? spec.min);
|
|
1575
|
+
const max = Number(control.max ?? spec.max);
|
|
1576
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min)
|
|
1577
|
+
errors.push(`interaction ${id} requires finite max > min`);
|
|
1578
|
+
if (control.step !== undefined &&
|
|
1579
|
+
(!isFiniteNumber(control.step) || control.step <= 0)) {
|
|
1580
|
+
errors.push(`interaction ${id} number step must be > 0`);
|
|
1581
|
+
}
|
|
1582
|
+
if (control.unit !== undefined && String(control.unit).length > 12)
|
|
1583
|
+
errors.push(`interaction ${id} unit must be 12 characters or fewer`);
|
|
1584
|
+
}
|
|
1585
|
+
else if (control.kind === 'choice') {
|
|
1586
|
+
if (!['enum', 'string'].includes(String(spec.type)))
|
|
1587
|
+
errors.push(`interaction ${id} choice control must target enum or string state`);
|
|
1588
|
+
const options = Array.isArray(control.options)
|
|
1589
|
+
? control.options.map(object)
|
|
1590
|
+
: [];
|
|
1591
|
+
if (!options.length || options.length > 64)
|
|
1592
|
+
errors.push(`interaction ${id} choice requires options (1..64)`);
|
|
1593
|
+
const optionValues = new Set();
|
|
1594
|
+
for (const option of options) {
|
|
1595
|
+
const value = String(option.value ?? '');
|
|
1596
|
+
if (!value || value.length > 80)
|
|
1597
|
+
errors.push(`interaction ${id} choice option value must be 1..80 characters`);
|
|
1598
|
+
else if (optionValues.has(value))
|
|
1599
|
+
errors.push(`interaction ${id} duplicate option "${value}"`);
|
|
1600
|
+
optionValues.add(value);
|
|
1601
|
+
const optionLabel = String(option.label ?? '');
|
|
1602
|
+
if (!optionLabel || optionLabel.length > 80)
|
|
1603
|
+
errors.push(`interaction ${id} choice option "${value}" requires a label of 1..80 characters`);
|
|
1604
|
+
if (option.reason !== undefined && String(option.reason).length > 160)
|
|
1605
|
+
errors.push(`interaction ${id} choice option "${value}" reason must be 160 characters or fewer`);
|
|
1606
|
+
if (option.disabled && !String(option.reason ?? '').trim())
|
|
1607
|
+
errors.push(`interaction ${id} disabled choice "${String(option.value)}" needs a reason`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
else if (control.kind === 'text') {
|
|
1611
|
+
if (spec.type !== 'string')
|
|
1612
|
+
errors.push(`interaction ${id} text control must target string state`);
|
|
1613
|
+
const maxLength = Number(control.maxLength ?? spec.maxLen ?? 512);
|
|
1614
|
+
if (!Number.isInteger(maxLength) || maxLength < 1 || maxLength > 4096)
|
|
1615
|
+
errors.push(`interaction ${id} text maxLength must be 1..4096`);
|
|
1616
|
+
if (typeof spec.maxLen === 'number' && maxLength > Number(spec.maxLen))
|
|
1617
|
+
errors.push(`interaction ${id} text maxLength cannot exceed state.${key} maxLen ${String(spec.maxLen)}`);
|
|
1618
|
+
if (control.placeholder !== undefined &&
|
|
1619
|
+
String(control.placeholder).length > 160) {
|
|
1620
|
+
errors.push(`interaction ${id} text placeholder must be 160 characters or fewer`);
|
|
1621
|
+
}
|
|
1622
|
+
if (control.inputMode !== undefined &&
|
|
1623
|
+
!TEXT_INPUT_MODES.has(String(control.inputMode))) {
|
|
1624
|
+
errors.push(`interaction ${id} has unsupported text inputMode "${String(control.inputMode)}"`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
else if (control.kind === undefined) {
|
|
1628
|
+
errors.push(`interaction ${id} control requires a kind (number|choice|text)`);
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
errors.push(`interaction ${id} uses unsupported control kind "${String(control.kind)}"`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
if (!actionAstError)
|
|
1635
|
+
(0, deviceActionValidation_1.validateDeviceInteractionAction)(interaction, state, errors);
|
|
1636
|
+
(0, deviceActionValidation_1.validateDeviceInteractionDisabledWhen)(interaction, state, errors);
|
|
1637
|
+
}
|
|
1638
|
+
const interactionsById = new Map();
|
|
1639
|
+
for (const interaction of interactions) {
|
|
1640
|
+
if (typeof interaction.id === 'string' && interaction.id.length > 0) {
|
|
1641
|
+
interactionsById.set(interaction.id, interaction);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
const components = Array.isArray(definition.components)
|
|
1645
|
+
? definition.components.map(object)
|
|
1646
|
+
: [];
|
|
1647
|
+
if (components.length > 32)
|
|
1648
|
+
errors.push('components exceed the 32-component Device budget');
|
|
1649
|
+
const bindings = object(model.nodeBindings);
|
|
1650
|
+
const apps = object(definition.apps);
|
|
1651
|
+
const sockets = new Set(Object.keys(object(definition.sockets)));
|
|
1652
|
+
const effects = new Set(Object.keys(object(definition.effects)));
|
|
1653
|
+
const assets = object(definition.assets);
|
|
1654
|
+
const audio = new Set(Object.keys(object(assets.audio)));
|
|
1655
|
+
const animations = new Set(Object.keys(object(assets.animations)));
|
|
1656
|
+
// `assets`, `sockets` and `effects` were entirely unread, so a character step
|
|
1657
|
+
// could name an animation that does not exist and still validate offline.
|
|
1658
|
+
for (const [kind, raw] of [
|
|
1659
|
+
['audio', assets.audio],
|
|
1660
|
+
['animations', assets.animations],
|
|
1661
|
+
]) {
|
|
1662
|
+
if (raw === undefined)
|
|
1663
|
+
continue;
|
|
1664
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1665
|
+
errors.push(`assets.${kind} must map a name to a source`);
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
for (const [name, source] of Object.entries(raw)) {
|
|
1669
|
+
if (!name.trim() || typeof source !== 'string' || !source.trim())
|
|
1670
|
+
errors.push(`assets.${kind}.${name} must name a non-empty source`);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
for (const [name, raw] of Object.entries(object(definition.sockets))) {
|
|
1674
|
+
const socket = object(raw);
|
|
1675
|
+
if (!isVec3(socket.position))
|
|
1676
|
+
errors.push(`socket ${name} requires a position [x, y, z]`);
|
|
1677
|
+
if (socket.rotationDeg !== undefined && !isVec3(socket.rotationDeg))
|
|
1678
|
+
errors.push(`socket ${name} rotationDeg must be [x, y, z]`);
|
|
1679
|
+
}
|
|
1680
|
+
for (const [name, raw] of Object.entries(object(definition.effects))) {
|
|
1681
|
+
validateDeviceVfxSpec(raw, sockets, `effects.${name}`, errors);
|
|
1682
|
+
}
|
|
1683
|
+
const collision = Array.isArray(definition.collision)
|
|
1684
|
+
? definition.collision.map(object)
|
|
1685
|
+
: [];
|
|
1686
|
+
if (collision.length > DEVICE_BUDGETS.maxCollisionNodes)
|
|
1687
|
+
errors.push(`collision: ${collision.length} nodes exceeds budget ${DEVICE_BUDGETS.maxCollisionNodes}`);
|
|
1688
|
+
const collisionIds = new Set();
|
|
1689
|
+
for (const solid of collision) {
|
|
1690
|
+
const solidId = String(solid.id ?? '');
|
|
1691
|
+
if (!solidId)
|
|
1692
|
+
errors.push('collision id must be a non-empty string');
|
|
1693
|
+
else if (collisionIds.has(solidId))
|
|
1694
|
+
errors.push(`collision: duplicate id "${solidId}"`);
|
|
1695
|
+
collisionIds.add(solidId);
|
|
1696
|
+
const node = String(solid.node ?? '');
|
|
1697
|
+
if (!node)
|
|
1698
|
+
errors.push(`collision ${solidId} requires a non-empty node`);
|
|
1699
|
+
else if (model.kind === 'procedural' && !nodes.has(node))
|
|
1700
|
+
errors.push(`collision.${solidId}: unknown node "${node}"`);
|
|
1701
|
+
}
|
|
1702
|
+
for (const [appId, raw] of Object.entries(apps)) {
|
|
1703
|
+
const app = object(raw);
|
|
1704
|
+
if (!['localWeb', 'remoteWeb', 'youtube', 'canvas', 'remoteImage', 'ownerImage'].includes(String(app.type)))
|
|
1705
|
+
errors.push(`app ${appId} has unsupported type "${String(app.type)}"`);
|
|
1706
|
+
if (app.type === 'canvas') {
|
|
1707
|
+
if (!DEVICE_CANVAS_APPS.has(String(app.entry)))
|
|
1708
|
+
errors.push(`app ${appId} names unknown canvas renderer "${String(app.entry)}" (known: ${[...DEVICE_CANVAS_APPS].join(', ')})`);
|
|
1709
|
+
if (Array.isArray(app.allowedOrigins) && app.allowedOrigins.length)
|
|
1710
|
+
errors.push(`app ${appId} is a canvas app and loads no web origin`);
|
|
1711
|
+
if (Array.isArray(app.permissions) && app.permissions.length)
|
|
1712
|
+
errors.push(`app ${appId} is a canvas app and needs no web permissions`);
|
|
1713
|
+
}
|
|
1714
|
+
if (app.type === 'remoteImage') {
|
|
1715
|
+
const imagePermissions = Array.isArray(app.permissions)
|
|
1716
|
+
? app.permissions.map(String)
|
|
1717
|
+
: [];
|
|
1718
|
+
if (!imagePermissions.includes('network.fetch'))
|
|
1719
|
+
errors.push(`app ${appId} is a remote image app and must declare network.fetch`);
|
|
1720
|
+
if (imagePermissions.some((permission) => permission !== 'network.fetch'))
|
|
1721
|
+
errors.push(`app ${appId} remote image apps only accept permission network.fetch`);
|
|
1722
|
+
}
|
|
1723
|
+
if (app.type === 'ownerImage') {
|
|
1724
|
+
// The owned copy's own image, moderated at upload and delivered by the
|
|
1725
|
+
// host through the placement. A package can neither point it anywhere
|
|
1726
|
+
// nor widen it.
|
|
1727
|
+
if (app.entry !== undefined)
|
|
1728
|
+
errors.push(`app ${appId} is an owner image app and takes no entry`);
|
|
1729
|
+
if (Array.isArray(app.allowedOrigins) && app.allowedOrigins.length)
|
|
1730
|
+
errors.push(`app ${appId} is an owner image app and loads from no origin`);
|
|
1731
|
+
if (Array.isArray(app.permissions) && app.permissions.length)
|
|
1732
|
+
errors.push(`app ${appId} is an owner image app and declares no permissions`);
|
|
1733
|
+
}
|
|
1734
|
+
if (app.type !== 'remoteImage' && app.type !== 'ownerImage' && !String(app.entry ?? '').trim())
|
|
1735
|
+
errors.push(`app ${appId} entry is required`);
|
|
1736
|
+
if (app.entry !== undefined && String(app.entry).length > 2048)
|
|
1737
|
+
errors.push(`app ${appId} entry must be 2048 characters or fewer`);
|
|
1738
|
+
if (app.permissions !== undefined && !Array.isArray(app.permissions)) {
|
|
1739
|
+
errors.push(`app ${appId} permissions must be an array`);
|
|
1740
|
+
}
|
|
1741
|
+
else {
|
|
1742
|
+
const permissions = Array.isArray(app.permissions) ? app.permissions.map(String) : [];
|
|
1743
|
+
if (permissions.length > 8)
|
|
1744
|
+
errors.push(`app ${appId} permissions exceed the 8-permission budget`);
|
|
1745
|
+
for (const permission of permissions) {
|
|
1746
|
+
if (!DEVICE_APP_PERMISSIONS.has(permission))
|
|
1747
|
+
errors.push(`app ${appId} has unsupported permission "${permission}"`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (app.type !== 'localWeb' && app.type !== 'canvas' && app.type !== 'ownerImage') {
|
|
1751
|
+
const origins = Array.isArray(app.allowedOrigins)
|
|
1752
|
+
? app.allowedOrigins.map(String)
|
|
1753
|
+
: [];
|
|
1754
|
+
if (origins.length === 0)
|
|
1755
|
+
errors.push(`app ${appId} requires allowedOrigins`);
|
|
1756
|
+
if (origins.length > 16)
|
|
1757
|
+
errors.push(`app ${appId} allowedOrigins exceeds the 16-origin budget`);
|
|
1758
|
+
for (const origin of origins) {
|
|
1759
|
+
try {
|
|
1760
|
+
if (new URL(origin).origin !== origin)
|
|
1761
|
+
errors.push(`app ${appId} allowed origin must not contain a path: ${origin}`);
|
|
1762
|
+
}
|
|
1763
|
+
catch {
|
|
1764
|
+
errors.push(`app ${appId} has invalid allowed origin: ${origin}`);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const canvasScreenNodes = new Set();
|
|
1770
|
+
const audioReactiveLightIds = new Set();
|
|
1771
|
+
const audioReactiveStreamPorts = new Set();
|
|
1772
|
+
const componentIds = new Set();
|
|
1773
|
+
let screenComponents = 0;
|
|
1774
|
+
let pointLightComponents = 0;
|
|
1775
|
+
const pointLightNodes = new Set();
|
|
1776
|
+
for (const component of components) {
|
|
1777
|
+
const componentId = String(component.id ?? '');
|
|
1778
|
+
if (!componentId)
|
|
1779
|
+
errors.push('component id must be a non-empty string');
|
|
1780
|
+
else if (componentIds.has(componentId))
|
|
1781
|
+
errors.push(`components: duplicate id "${componentId}"`);
|
|
1782
|
+
componentIds.add(componentId);
|
|
1783
|
+
// The host's `components` is a discriminated union over six kinds. Without
|
|
1784
|
+
// an allowlist an unrecognized `type` fell through every branch below and
|
|
1785
|
+
// was validated not at all — which is how `audioReactiveLight` shipped with
|
|
1786
|
+
// required fields nobody read.
|
|
1787
|
+
if (!COMPONENT_TYPES.has(String(component.type))) {
|
|
1788
|
+
errors.push(`component ${componentId} has unsupported type "${String(component.type)}" (one of ${exports.DEVICE_COMPONENT_TYPES.join('|')})`);
|
|
1789
|
+
}
|
|
1790
|
+
// `node` is required on the host's `slider`, `pointLight` and `screen`
|
|
1791
|
+
// variants (`validate.ts:423-527`); `clockHands`, `audioReactiveLight` and
|
|
1792
|
+
// `characterSpot` have no such field. The loop below only resolves a node
|
|
1793
|
+
// reference when one is present, so a component with none passed here and
|
|
1794
|
+
// was rejected at load with `components.N: Invalid input`.
|
|
1795
|
+
if (['slider', 'pointLight', 'screen'].includes(String(component.type)) &&
|
|
1796
|
+
!isNonEmptyName(component.node)) {
|
|
1797
|
+
errors.push(`component ${componentId} (${String(component.type)}) requires a non-empty node`);
|
|
1798
|
+
}
|
|
1799
|
+
const semanticNodes = [
|
|
1800
|
+
'node',
|
|
1801
|
+
'hourNode',
|
|
1802
|
+
'minuteNode',
|
|
1803
|
+
'secondNode',
|
|
1804
|
+
'secondCounterweightNode',
|
|
1805
|
+
];
|
|
1806
|
+
for (const field of semanticNodes) {
|
|
1807
|
+
if (!component[field])
|
|
1808
|
+
continue;
|
|
1809
|
+
const semantic = String(component[field]);
|
|
1810
|
+
const authored = String(bindings[semantic] ?? semantic);
|
|
1811
|
+
if (nodeNames.length && !nodes.has(authored))
|
|
1812
|
+
errors.push(`component ${String(component.id)} ${field} resolves to missing GLB node "${authored}"`);
|
|
1813
|
+
}
|
|
1814
|
+
if (component.type === 'screen') {
|
|
1815
|
+
screenComponents += 1;
|
|
1816
|
+
if (object(apps[String(component.app ?? '')]).type === 'canvas') {
|
|
1817
|
+
const node = String(component.node ?? '');
|
|
1818
|
+
if (canvasScreenNodes.has(node))
|
|
1819
|
+
errors.push(`screen ${String(component.id)} has duplicate canvas screen node "${node}"`);
|
|
1820
|
+
canvasScreenNodes.add(node);
|
|
1821
|
+
for (const field of ['sourceStateKey', 'playbackStateKey', 'volumeStateKey', 'powerStateKey']) {
|
|
1822
|
+
if (component[field])
|
|
1823
|
+
errors.push(`screen ${String(component.id)} canvas screens do not accept media controls (${field})`);
|
|
1824
|
+
}
|
|
1825
|
+
if (component.interactive)
|
|
1826
|
+
errors.push(`screen ${String(component.id)} canvas screens take input through declared interactions`);
|
|
1827
|
+
}
|
|
1828
|
+
if (object(apps[String(component.app ?? '')]).type === 'ownerImage') {
|
|
1829
|
+
if (component.interactive)
|
|
1830
|
+
errors.push(`screen ${String(component.id)} owner image screens take input through the host's Set image action`);
|
|
1831
|
+
for (const field of ['sourceStateKey', 'playbackStateKey', 'volumeStateKey', 'powerStateKey']) {
|
|
1832
|
+
if (component[field])
|
|
1833
|
+
errors.push(`screen ${String(component.id)} owner image screens show the copy's own image, not state (${field})`);
|
|
1834
|
+
}
|
|
1835
|
+
if (component.timeline)
|
|
1836
|
+
errors.push(`screen ${String(component.id)} owner image screens do not accept a media timeline`);
|
|
1837
|
+
}
|
|
1838
|
+
if (object(apps[String(component.app ?? '')]).type === 'remoteImage') {
|
|
1839
|
+
if (component.interactive)
|
|
1840
|
+
errors.push(`screen ${String(component.id)} remote image screens take input through declared interactions`);
|
|
1841
|
+
for (const field of ['playbackStateKey', 'volumeStateKey']) {
|
|
1842
|
+
if (component[field])
|
|
1843
|
+
errors.push(`screen ${String(component.id)} remote image screens do not accept media controls (${field})`);
|
|
1844
|
+
}
|
|
1845
|
+
if (component.timeline)
|
|
1846
|
+
errors.push(`screen ${String(component.id)} remote image screens do not accept a media timeline`);
|
|
1847
|
+
const sourceKey = String(component.sourceStateKey ?? '');
|
|
1848
|
+
if (!sourceKey)
|
|
1849
|
+
errors.push(`screen ${String(component.id)} remote image screens require sourceStateKey (the image URL)`);
|
|
1850
|
+
else if (object(state[sourceKey]).type !== 'string')
|
|
1851
|
+
errors.push(`screen ${String(component.id)} sourceStateKey "${sourceKey}" must reference string state`);
|
|
1852
|
+
if (component.powerStateKey && object(state[String(component.powerStateKey)]).type !== 'boolean')
|
|
1853
|
+
errors.push(`screen ${String(component.id)} powerStateKey "${String(component.powerStateKey)}" must be boolean`);
|
|
1854
|
+
}
|
|
1855
|
+
if (!apps[String(component.app ?? '')])
|
|
1856
|
+
errors.push(`screen ${String(component.id)} references unknown app "${String(component.app)}"`);
|
|
1857
|
+
for (const field of [
|
|
1858
|
+
'powerStateKey',
|
|
1859
|
+
'sourceStateKey',
|
|
1860
|
+
'playbackStateKey',
|
|
1861
|
+
'volumeStateKey',
|
|
1862
|
+
]) {
|
|
1863
|
+
if (component[field] && !state[String(component[field])])
|
|
1864
|
+
errors.push(`screen ${String(component.id)} references unknown state "${String(component[field])}"`);
|
|
1865
|
+
}
|
|
1866
|
+
if (!(Number(component.widthM) > 0) || !(Number(component.heightM) > 0))
|
|
1867
|
+
errors.push(`screen ${String(component.id)} requires positive widthM and heightM`);
|
|
1868
|
+
if (Number(component.widthM) > 20)
|
|
1869
|
+
errors.push(`screen ${String(component.id)} widthM must be <= 20`);
|
|
1870
|
+
if (Number(component.heightM) > 20)
|
|
1871
|
+
errors.push(`screen ${String(component.id)} heightM must be <= 20`);
|
|
1872
|
+
if (component.suspendDistanceM !== undefined &&
|
|
1873
|
+
(!isFiniteNumber(component.suspendDistanceM) ||
|
|
1874
|
+
component.suspendDistanceM <= 0 ||
|
|
1875
|
+
component.suspendDistanceM > 200)) {
|
|
1876
|
+
errors.push(`screen ${String(component.id)} suspendDistanceM must be > 0 and <= 200`);
|
|
1877
|
+
}
|
|
1878
|
+
(0, deviceActionValidation_1.validateDeviceScreenTimeline)(component, state, interactionsById, errors);
|
|
1879
|
+
}
|
|
1880
|
+
if (component.type === 'pointLight') {
|
|
1881
|
+
pointLightComponents += 1;
|
|
1882
|
+
const node = typeof component.node === 'string' ? component.node : '';
|
|
1883
|
+
if (node.length === 0) {
|
|
1884
|
+
errors.push(`point light ${String(component.id)} requires a non-empty node`);
|
|
1885
|
+
}
|
|
1886
|
+
else if (pointLightNodes.has(node)) {
|
|
1887
|
+
errors.push(`point light ${String(component.id)} duplicates node "${node}"`);
|
|
1888
|
+
}
|
|
1889
|
+
if (node.length > 0)
|
|
1890
|
+
pointLightNodes.add(node);
|
|
1891
|
+
const stateKey = String(component.stateKey ?? '');
|
|
1892
|
+
if (object(state[stateKey]).type !== 'boolean') {
|
|
1893
|
+
errors.push(`point light ${String(component.id)} stateKey "${stateKey}" must be boolean`);
|
|
1894
|
+
}
|
|
1895
|
+
if (component.colorStateKey !== undefined) {
|
|
1896
|
+
const colorStateKey = String(component.colorStateKey ?? '');
|
|
1897
|
+
const colorState = object(state[colorStateKey]);
|
|
1898
|
+
const values = Array.isArray(colorState.values)
|
|
1899
|
+
? colorState.values.map(String)
|
|
1900
|
+
: [];
|
|
1901
|
+
if (colorState.type !== 'enum') {
|
|
1902
|
+
errors.push(`point light ${String(component.id)} colorStateKey "${colorStateKey}" must reference enum state`);
|
|
1903
|
+
}
|
|
1904
|
+
else if (values.length === 0 ||
|
|
1905
|
+
values.some((value) => !/^#[0-9a-fA-F]{6}$/.test(value))) {
|
|
1906
|
+
errors.push(`point light ${String(component.id)} colorStateKey "${colorStateKey}" enum values must all be #RRGGBB`);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (component.brightnessStateKey !== undefined) {
|
|
1910
|
+
const brightnessStateKey = String(component.brightnessStateKey ?? '');
|
|
1911
|
+
const brightnessState = object(state[brightnessStateKey]);
|
|
1912
|
+
if (!['integer', 'float'].includes(String(brightnessState.type))) {
|
|
1913
|
+
errors.push(`point light ${String(component.id)} brightnessStateKey "${brightnessStateKey}" must reference numeric state`);
|
|
1914
|
+
}
|
|
1915
|
+
else if (brightnessState.min !== 0 ||
|
|
1916
|
+
brightnessState.max !== 100) {
|
|
1917
|
+
errors.push(`point light ${String(component.id)} brightnessStateKey "${brightnessStateKey}" must declare min 0 and max 100`);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const color = component.color;
|
|
1921
|
+
if (!((typeof color === 'number' &&
|
|
1922
|
+
Number.isInteger(color) &&
|
|
1923
|
+
color >= 0 &&
|
|
1924
|
+
color <= 0xffffff) ||
|
|
1925
|
+
(typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)))) {
|
|
1926
|
+
errors.push(`point light ${String(component.id)} color must be #RRGGBB or a 24-bit integer`);
|
|
1927
|
+
}
|
|
1928
|
+
if (typeof component.intensityCandela !== 'number' ||
|
|
1929
|
+
!Number.isFinite(component.intensityCandela) ||
|
|
1930
|
+
component.intensityCandela <= 0 ||
|
|
1931
|
+
component.intensityCandela > 5_000) {
|
|
1932
|
+
errors.push(`point light ${String(component.id)} intensityCandela must be > 0 and <= 5000`);
|
|
1933
|
+
}
|
|
1934
|
+
if (typeof component.distanceM !== 'number' ||
|
|
1935
|
+
!Number.isFinite(component.distanceM) ||
|
|
1936
|
+
component.distanceM <= 0 ||
|
|
1937
|
+
component.distanceM > 25) {
|
|
1938
|
+
errors.push(`point light ${String(component.id)} distanceM must be > 0 and <= 25`);
|
|
1939
|
+
}
|
|
1940
|
+
if (component.decay !== undefined &&
|
|
1941
|
+
(typeof component.decay !== 'number' ||
|
|
1942
|
+
!Number.isFinite(component.decay) ||
|
|
1943
|
+
component.decay < 0 ||
|
|
1944
|
+
component.decay > 4)) {
|
|
1945
|
+
errors.push(`point light ${String(component.id)} decay must be between 0 and 4`);
|
|
1946
|
+
}
|
|
1947
|
+
if (component.emissiveIntensity !== undefined &&
|
|
1948
|
+
(typeof component.emissiveIntensity !== 'number' ||
|
|
1949
|
+
!Number.isFinite(component.emissiveIntensity) ||
|
|
1950
|
+
component.emissiveIntensity < 0 ||
|
|
1951
|
+
component.emissiveIntensity > 10)) {
|
|
1952
|
+
errors.push(`point light ${String(component.id)} emissiveIntensity must be between 0 and 10`);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
if (component.type === 'slider') {
|
|
1956
|
+
const label = `slider ${componentId}`;
|
|
1957
|
+
if (!['integer', 'float'].includes(String(object(state[String(component.stateKey ?? '')]).type))) {
|
|
1958
|
+
errors.push(`${label} stateKey "${String(component.stateKey)}" must be numeric`);
|
|
1959
|
+
}
|
|
1960
|
+
if (!['x', 'y', 'z'].includes(String(component.axis)))
|
|
1961
|
+
errors.push(`${label} axis must be x, y or z`);
|
|
1962
|
+
if (!isFiniteNumber(component.minOffsetM))
|
|
1963
|
+
errors.push(`${label} requires a finite minOffsetM`);
|
|
1964
|
+
if (!isFiniteNumber(component.maxOffsetM))
|
|
1965
|
+
errors.push(`${label} requires a finite maxOffsetM`);
|
|
1966
|
+
if (!isFiniteNumber(component.durationMs) ||
|
|
1967
|
+
component.durationMs < 16 ||
|
|
1968
|
+
component.durationMs > 60_000) {
|
|
1969
|
+
errors.push(`${label} durationMs must be between 16 and 60000`);
|
|
1970
|
+
}
|
|
1971
|
+
checkEasing(component.easing, label, errors);
|
|
1972
|
+
}
|
|
1973
|
+
if (component.type === 'clockHands') {
|
|
1974
|
+
const label = `clock hands ${componentId}`;
|
|
1975
|
+
const handNodes = [
|
|
1976
|
+
'hourNode',
|
|
1977
|
+
'minuteNode',
|
|
1978
|
+
'secondNode',
|
|
1979
|
+
'secondCounterweightNode',
|
|
1980
|
+
].filter((field) => String(component[field] ?? '').length > 0);
|
|
1981
|
+
if (handNodes.length === 0)
|
|
1982
|
+
errors.push(`${label} requires at least one hand node`);
|
|
1983
|
+
if (component.timeZone !== undefined &&
|
|
1984
|
+
!['local', 'utc'].includes(String(component.timeZone))) {
|
|
1985
|
+
errors.push(`${label} timeZone must be local or utc`);
|
|
1986
|
+
}
|
|
1987
|
+
if (component.utcOffsetHours !== undefined) {
|
|
1988
|
+
if (!isFiniteNumber(component.utcOffsetHours) ||
|
|
1989
|
+
component.utcOffsetHours < -12 ||
|
|
1990
|
+
component.utcOffsetHours > 14) {
|
|
1991
|
+
errors.push(`${label} utcOffsetHours must be between -12 and 14`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (component.type === 'audioReactiveLight') {
|
|
1996
|
+
const label = `audio reactive light ${componentId}`;
|
|
1997
|
+
const lightComponent = String(component.lightComponent ?? '');
|
|
1998
|
+
if (!lightComponent) {
|
|
1999
|
+
errors.push(`${label} requires a non-empty lightComponent`);
|
|
2000
|
+
}
|
|
2001
|
+
else if (audioReactiveLightIds.has(lightComponent)) {
|
|
2002
|
+
errors.push(`${label} duplicates audio-reactive light "${lightComponent}"`);
|
|
2003
|
+
}
|
|
2004
|
+
audioReactiveLightIds.add(lightComponent);
|
|
2005
|
+
const streamPort = String(component.streamPort ?? '');
|
|
2006
|
+
if (!streamPort) {
|
|
2007
|
+
errors.push(`${label} requires a non-empty streamPort`);
|
|
2008
|
+
}
|
|
2009
|
+
else if (audioReactiveStreamPorts.has(streamPort)) {
|
|
2010
|
+
errors.push(`${label} duplicates audio-reactive stream port "${streamPort}"`);
|
|
2011
|
+
}
|
|
2012
|
+
audioReactiveStreamPorts.add(streamPort);
|
|
2013
|
+
const enabledStateKey = String(component.enabledStateKey ?? '');
|
|
2014
|
+
const enabledState = object(state[enabledStateKey]);
|
|
2015
|
+
if (enabledState.type !== 'boolean') {
|
|
2016
|
+
errors.push(`${label} enabledStateKey "${enabledStateKey}" must be boolean`);
|
|
2017
|
+
}
|
|
2018
|
+
else if (enabledState.default !== false) {
|
|
2019
|
+
// An audio-reactive light that starts enabled would drive a light the
|
|
2020
|
+
// creator never switched on, so the host demands a false default.
|
|
2021
|
+
errors.push(`${label} enabledStateKey "${enabledStateKey}" must default to false`);
|
|
2022
|
+
}
|
|
2023
|
+
else if (enabledState.persist !== true) {
|
|
2024
|
+
errors.push(`${label} enabledStateKey "${enabledStateKey}" must be durable`);
|
|
2025
|
+
}
|
|
2026
|
+
if (!components.some((candidate) => candidate.type === 'pointLight' &&
|
|
2027
|
+
String(candidate.id ?? '') === lightComponent)) {
|
|
2028
|
+
errors.push(`${label} references unknown point light "${lightComponent}"`);
|
|
2029
|
+
}
|
|
2030
|
+
const ranges = [
|
|
2031
|
+
['floor', 0, 1, true],
|
|
2032
|
+
['sensitivity', 0, 20, false],
|
|
2033
|
+
['attackMs', 16, 2_000, true],
|
|
2034
|
+
['releaseMs', 16, 5_000, true],
|
|
2035
|
+
];
|
|
2036
|
+
for (const [field, min, max, inclusiveMin] of ranges) {
|
|
2037
|
+
const value = component[field];
|
|
2038
|
+
if (value === undefined)
|
|
2039
|
+
continue;
|
|
2040
|
+
if (!isFiniteNumber(value) ||
|
|
2041
|
+
(inclusiveMin ? value < min : value <= min) ||
|
|
2042
|
+
value > max) {
|
|
2043
|
+
errors.push(`${label} ${field} must be ${inclusiveMin ? '>=' : '>'} ${min} and <= ${max}`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (component.fftSize !== undefined &&
|
|
2047
|
+
(typeof component.fftSize !== 'number' ||
|
|
2048
|
+
!DEVICE_FFT_SIZES.has(component.fftSize))) {
|
|
2049
|
+
errors.push(`${label} fftSize must be one of ${[...DEVICE_FFT_SIZES].join('|')}`);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
if (component.type === 'characterSpot') {
|
|
2053
|
+
if (!['sit', 'lie'].includes(String(component.pose)))
|
|
2054
|
+
errors.push(`character spot ${String(component.id)} pose must be sit or lie`);
|
|
2055
|
+
if (!isVec3(component.position))
|
|
2056
|
+
errors.push(`character spot ${String(component.id)} requires position [x, y, z]`);
|
|
2057
|
+
if (typeof component.facingYawDeg !== 'number' ||
|
|
2058
|
+
!Number.isFinite(component.facingYawDeg)) {
|
|
2059
|
+
errors.push(`character spot ${String(component.id)} requires finite facingYawDeg`);
|
|
2060
|
+
}
|
|
2061
|
+
if (component.clearanceM !== undefined &&
|
|
2062
|
+
(typeof component.clearanceM !== 'number' ||
|
|
2063
|
+
!Number.isFinite(component.clearanceM) ||
|
|
2064
|
+
component.clearanceM < 0 ||
|
|
2065
|
+
component.clearanceM > 2)) {
|
|
2066
|
+
errors.push(`character spot ${String(component.id)} clearanceM must be between 0 and 2`);
|
|
2067
|
+
}
|
|
2068
|
+
if (component.exitPosition !== undefined && !isVec3(component.exitPosition))
|
|
2069
|
+
errors.push(`character spot ${String(component.id)} exitPosition must be [x, y, z]`);
|
|
2070
|
+
if (component.label !== undefined &&
|
|
2071
|
+
(typeof component.label !== 'string' ||
|
|
2072
|
+
component.label.trim().length === 0 ||
|
|
2073
|
+
component.label.length > 40)) {
|
|
2074
|
+
errors.push(`character spot ${String(component.id)} label must be 1..40 characters`);
|
|
2075
|
+
}
|
|
2076
|
+
for (const field of ['fallbackAnimation', 'fallbackSocket']) {
|
|
2077
|
+
if (component[field] !== undefined && !String(component[field]).trim())
|
|
2078
|
+
errors.push(`character spot ${String(component.id)} ${field} must be a non-empty name`);
|
|
2079
|
+
}
|
|
2080
|
+
// Field names matching is not enough: the host resolves both of these
|
|
2081
|
+
// against the package's own declarations, so a dangling name that reads
|
|
2082
|
+
// correctly still fails to load.
|
|
2083
|
+
if (isNonEmptyName(component.fallbackAnimation) &&
|
|
2084
|
+
!animations.has(String(component.fallbackAnimation))) {
|
|
2085
|
+
errors.push(`character spot ${String(component.id)} references unknown fallback animation "${String(component.fallbackAnimation)}"`);
|
|
2086
|
+
}
|
|
2087
|
+
if (isNonEmptyName(component.fallbackSocket) &&
|
|
2088
|
+
!sockets.has(String(component.fallbackSocket))) {
|
|
2089
|
+
errors.push(`character spot ${String(component.id)} references unknown fallback socket "${String(component.fallbackSocket)}"`);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
if (pointLightComponents > 4) {
|
|
2094
|
+
errors.push(`components contain ${pointLightComponents} point lights, exceeding the 4-light Device budget`);
|
|
2095
|
+
}
|
|
2096
|
+
if (screenComponents > DEVICE_BUDGETS.maxScreens) {
|
|
2097
|
+
errors.push(`components contain ${screenComponents} screens, exceeding the ${DEVICE_BUDGETS.maxScreens}-screen Device budget`);
|
|
2098
|
+
}
|
|
2099
|
+
const ports = Array.isArray(definition.ports)
|
|
2100
|
+
? definition.ports.map(object)
|
|
2101
|
+
: [];
|
|
2102
|
+
if (ports.length > DEVICE_BUDGETS.maxPorts)
|
|
2103
|
+
errors.push(`ports: ${ports.length} exceeds budget ${DEVICE_BUDGETS.maxPorts}`);
|
|
2104
|
+
const declaredInterfaces = Array.isArray(definition.interfaces)
|
|
2105
|
+
? definition.interfaces
|
|
2106
|
+
: [];
|
|
2107
|
+
if (declaredInterfaces.length > DEVICE_BUDGETS.maxInterfaces)
|
|
2108
|
+
errors.push(`interfaces: ${declaredInterfaces.length} exceeds budget ${DEVICE_BUDGETS.maxInterfaces}`);
|
|
2109
|
+
const portIds = new Set();
|
|
2110
|
+
for (const port of ports) {
|
|
2111
|
+
const id = String(port.id ?? '');
|
|
2112
|
+
if (!id || portIds.has(id))
|
|
2113
|
+
errors.push(`port id "${id}" is missing or duplicated`);
|
|
2114
|
+
portIds.add(id);
|
|
2115
|
+
if (id.length > 64)
|
|
2116
|
+
errors.push(`port ${id} id must be 64 characters or fewer`);
|
|
2117
|
+
if (!DEVICE_PORT_DIRECTIONS.has(String(port.direction)))
|
|
2118
|
+
errors.push(`port ${id} has unsupported direction`);
|
|
2119
|
+
if (!['value', 'event', 'command', 'stream'].includes(String(port.kind)))
|
|
2120
|
+
errors.push(`port ${id} has unsupported kind`);
|
|
2121
|
+
if (!['boolean', 'number', 'string', 'media.audio', 'media.video'].includes(String(port.dataType)))
|
|
2122
|
+
errors.push(`port ${id} has unsupported dataType`);
|
|
2123
|
+
// The host makes `interface` optional and bounds it to `min(1).max(128)`
|
|
2124
|
+
// when present, so an absent interface is legal and a blank one is not.
|
|
2125
|
+
// Requiring it here rejected ports the client loads without complaint.
|
|
2126
|
+
if (port.interface !== undefined &&
|
|
2127
|
+
(typeof port.interface !== 'string' || !port.interface.trim())) {
|
|
2128
|
+
errors.push(`port ${id} requires interface`);
|
|
2129
|
+
}
|
|
2130
|
+
if (typeof port.interface === 'string' && port.interface.length > 128)
|
|
2131
|
+
errors.push(`port ${id} interface must be 128 characters or fewer`);
|
|
2132
|
+
if (port.semantic !== undefined && !String(port.semantic).trim())
|
|
2133
|
+
errors.push(`port ${id} semantic must be a non-empty name`);
|
|
2134
|
+
if (typeof port.semantic === 'string' && port.semantic.length > 128)
|
|
2135
|
+
errors.push(`port ${id} semantic must be 128 characters or fewer`);
|
|
2136
|
+
if (port.authority !== undefined &&
|
|
2137
|
+
!DEVICE_PORT_AUTHORITIES.has(String(port.authority))) {
|
|
2138
|
+
errors.push(`port ${id} has unsupported authority`);
|
|
2139
|
+
}
|
|
2140
|
+
if (port.maxLinks !== undefined &&
|
|
2141
|
+
(!Number.isInteger(port.maxLinks) || Number(port.maxLinks) < 1 || Number(port.maxLinks) > 64)) {
|
|
2142
|
+
errors.push(`port ${id} maxLinks must be 1..64`);
|
|
2143
|
+
}
|
|
2144
|
+
if (port.stateKey) {
|
|
2145
|
+
const stateKey = String(port.stateKey);
|
|
2146
|
+
const spec = object(state[stateKey]);
|
|
2147
|
+
if (!Object.keys(spec).length) {
|
|
2148
|
+
errors.push(`port ${id} references unknown state "${stateKey}"`);
|
|
2149
|
+
}
|
|
2150
|
+
else {
|
|
2151
|
+
// A value port carries the state's own type across the bus; declaring
|
|
2152
|
+
// `string` over numeric state is accepted offline and refused at load.
|
|
2153
|
+
const expected = spec.type === 'boolean'
|
|
2154
|
+
? 'boolean'
|
|
2155
|
+
: spec.type === 'integer' || spec.type === 'float'
|
|
2156
|
+
? 'number'
|
|
2157
|
+
: 'string';
|
|
2158
|
+
if (String(port.dataType) !== expected) {
|
|
2159
|
+
errors.push(`port ${id} dataType "${String(port.dataType)}" does not match ${String(spec.type)} state`);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
if (port.kind === 'value' && !port.stateKey)
|
|
2164
|
+
errors.push(`value port ${id} requires stateKey`);
|
|
2165
|
+
if (port.kind !== 'value' && port.stateKey)
|
|
2166
|
+
errors.push(`non-value port ${id} must not declare stateKey`);
|
|
2167
|
+
if (port.kind === 'stream' &&
|
|
2168
|
+
!['media.audio', 'media.video'].includes(String(port.dataType))) {
|
|
2169
|
+
errors.push(`stream port ${id} requires media.audio or media.video dataType`);
|
|
2170
|
+
}
|
|
2171
|
+
// `routability` is stream-only on the host (`validate.ts:1466-1467`) and
|
|
2172
|
+
// its vocabulary is exactly the host's three-member enum
|
|
2173
|
+
// (`validate.ts:330`). `embedded-only` is a legal declaration, not an
|
|
2174
|
+
// error: the host links it and carries the status on the envelope
|
|
2175
|
+
// (`links.ts` `publishStream`), so only a non-stream port or an unknown
|
|
2176
|
+
// member is refused here.
|
|
2177
|
+
if (port.routability !== undefined) {
|
|
2178
|
+
if (port.kind !== 'stream')
|
|
2179
|
+
errors.push(`port ${id} routability is stream-only`);
|
|
2180
|
+
else if (!DEVICE_STREAM_ROUTABILITY.has(String(port.routability)))
|
|
2181
|
+
errors.push(`port ${id} has unsupported stream routability`);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
// An audio-reactive light listens on a bus port, and the host refuses a port
|
|
2185
|
+
// that is not the local routable `helix.audio/1` input it expects.
|
|
2186
|
+
for (const component of components) {
|
|
2187
|
+
if (component.type !== 'audioReactiveLight')
|
|
2188
|
+
continue;
|
|
2189
|
+
const streamPort = String(component.streamPort ?? '');
|
|
2190
|
+
if (!streamPort)
|
|
2191
|
+
continue;
|
|
2192
|
+
const port = ports.find((candidate) => String(candidate.id ?? '') === streamPort);
|
|
2193
|
+
if (!port ||
|
|
2194
|
+
port.direction === 'output' ||
|
|
2195
|
+
port.kind !== 'stream' ||
|
|
2196
|
+
port.dataType !== 'media.audio' ||
|
|
2197
|
+
port.interface !== 'helix.audio/1' ||
|
|
2198
|
+
port.authority !== 'local' ||
|
|
2199
|
+
(port.routability ?? 'routable') !== 'routable') {
|
|
2200
|
+
errors.push(`audio reactive light ${String(component.id)} streamPort "${streamPort}" must be a local routable helix.audio/1 media.audio input`);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const graph = object(definition.graph);
|
|
2204
|
+
const rawTriggers = graph.triggers;
|
|
2205
|
+
// `triggers` is a non-optional key on the host's `graph` object
|
|
2206
|
+
// (`validate.ts:528-530`), so `graph: {}` is rejected at load — and it is
|
|
2207
|
+
// also the one shape that would otherwise read as "a graph that does
|
|
2208
|
+
// nothing" and pass here.
|
|
2209
|
+
if (rawTriggers === undefined)
|
|
2210
|
+
errors.push('graph.triggers is required');
|
|
2211
|
+
else if (!Array.isArray(rawTriggers))
|
|
2212
|
+
errors.push('graph.triggers must be an array');
|
|
2213
|
+
const triggers = Array.isArray(rawTriggers) ? rawTriggers : [];
|
|
2214
|
+
if (triggers.length > DEVICE_BUDGETS.maxTriggers)
|
|
2215
|
+
errors.push(`graph: ${triggers.length} triggers exceeds budget ${DEVICE_BUDGETS.maxTriggers}`);
|
|
2216
|
+
const characterSpotIds = new Set(components
|
|
2217
|
+
.filter((component) => component.type === 'characterSpot')
|
|
2218
|
+
.map((component) => String(component.id ?? '')));
|
|
2219
|
+
// Capability requirements driven by what the graph *does*, collected while
|
|
2220
|
+
// the steps are walked — so a character step nested inside a branch counts
|
|
2221
|
+
// exactly as a top-level one does, instead of being missed by a scan that
|
|
2222
|
+
// only looked at the four character kinds.
|
|
2223
|
+
const graphUsage = {
|
|
2224
|
+
nodeMotion: components.some((component) => component.type === 'slider'),
|
|
2225
|
+
audioPlayback: components.some((component) => component.type === 'audioReactiveLight'),
|
|
2226
|
+
vfx: false,
|
|
2227
|
+
posture: characterSpotIds.size > 0,
|
|
2228
|
+
hand: false,
|
|
2229
|
+
animation: components.some((component) => component.type === 'characterSpot' &&
|
|
2230
|
+
component.fallbackAnimation !== undefined),
|
|
2231
|
+
};
|
|
2232
|
+
const graphContext = {
|
|
2233
|
+
state,
|
|
2234
|
+
nodes,
|
|
2235
|
+
nodesKnown: nodeNames.length > 0,
|
|
2236
|
+
bindings,
|
|
2237
|
+
sockets,
|
|
2238
|
+
effects,
|
|
2239
|
+
audio,
|
|
2240
|
+
animations,
|
|
2241
|
+
characterSpots: characterSpotIds,
|
|
2242
|
+
errors,
|
|
2243
|
+
usage: graphUsage,
|
|
2244
|
+
};
|
|
2245
|
+
triggers.forEach((raw, index) => {
|
|
2246
|
+
const triggerPath = `graph.triggers.${index}`;
|
|
2247
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2248
|
+
errors.push(`${triggerPath}: trigger must be an object`);
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const trigger = object(raw);
|
|
2252
|
+
const on = String(trigger.on ?? '');
|
|
2253
|
+
if (!TRIGGER_KINDS.has(on)) {
|
|
2254
|
+
errors.push(`${triggerPath}: unsupported trigger "${on}" (one of ${exports.DEVICE_TRIGGER_KINDS.join('|')})`);
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
if (on === 'interact') {
|
|
2258
|
+
if (interactionsById.get(String(trigger.interaction))?.presentation !== undefined)
|
|
2259
|
+
errors.push(`${triggerPath}: webPanel presentation cannot be a graph interaction trigger`);
|
|
2260
|
+
if (!interactionIds.has(String(trigger.interaction))) {
|
|
2261
|
+
errors.push(`graph references undeclared interaction "${String(trigger.interaction)}"`);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
else if (on === 'stateChanged') {
|
|
2265
|
+
if (!isNonEmptyName(trigger.key))
|
|
2266
|
+
errors.push(`${triggerPath}: stateChanged requires a non-empty key`);
|
|
2267
|
+
}
|
|
2268
|
+
else if (on === 'signal') {
|
|
2269
|
+
if (!isNonEmptyName(trigger.name))
|
|
2270
|
+
errors.push(`${triggerPath}: signal requires a non-empty name`);
|
|
2271
|
+
}
|
|
2272
|
+
else if (on === 'playerNear') {
|
|
2273
|
+
if (!isFiniteNumber(trigger.distanceM) ||
|
|
2274
|
+
trigger.distanceM <= 0 ||
|
|
2275
|
+
trigger.distanceM > 100) {
|
|
2276
|
+
errors.push(`${triggerPath}: playerNear distanceM must be > 0 and <= 100`);
|
|
2277
|
+
}
|
|
2278
|
+
if (!['enter', 'exit'].includes(String(trigger.phase)))
|
|
2279
|
+
errors.push(`${triggerPath}: playerNear phase must be enter or exit`);
|
|
2280
|
+
}
|
|
2281
|
+
// The host's `do` array is non-optional on every trigger variant.
|
|
2282
|
+
if (trigger.do === undefined)
|
|
2283
|
+
errors.push(`${triggerPath}: every trigger requires a do array`);
|
|
2284
|
+
else {
|
|
2285
|
+
validateGraphSteps(trigger.do, graphContext, `${triggerPath}.do`, 0);
|
|
2286
|
+
let handBudgetInvalid = false;
|
|
2287
|
+
if (graphHasHandStep(trigger.do)) {
|
|
2288
|
+
const metrics = handGraphExecutionMetrics(trigger.do);
|
|
2289
|
+
for (const metric of metrics) {
|
|
2290
|
+
if (metric.steps >= DEVICE_BUDGETS.maxStepsPerFire) {
|
|
2291
|
+
errors.push(`${triggerPath}: a hand reach/release execution path can exhaust the ${DEVICE_BUDGETS.maxStepsPerFire}-step runtime budget`);
|
|
2292
|
+
handBudgetInvalid = true;
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (handGraphMaxPendingDelays(trigger.do) >
|
|
2297
|
+
DEVICE_BUDGETS.maxConcurrentDelays) {
|
|
2298
|
+
errors.push(`${triggerPath}: a hand reach/release execution path can exceed the ${DEVICE_BUDGETS.maxConcurrentDelays}-delay runtime limit`);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
// Do not enumerate a graph the runtime cannot finish. In particular,
|
|
2302
|
+
// nested 64-repeat bodies have an exponential concrete path count even
|
|
2303
|
+
// though the structural metric above proves they abort at 256 steps.
|
|
2304
|
+
const endings = handBudgetInvalid ? [] : handReachPathEnds(trigger.do);
|
|
2305
|
+
for (const hand of ['left', 'right']) {
|
|
2306
|
+
if (endings.some((path) => path.has(hand))) {
|
|
2307
|
+
errors.push(`${triggerPath}: a reachable path leaves ${hand} reachCharacterHand without releaseCharacterHand`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
// `compatibility` itself is optional on the host, and its `hosts` array is a
|
|
2313
|
+
// `min(1)` enum — NOT "both home and world". Demanding both here rejected a
|
|
2314
|
+
// world-only Device the host loads happily.
|
|
2315
|
+
if (definition.compatibility !== undefined) {
|
|
2316
|
+
const rawHosts = object(definition.compatibility).hosts;
|
|
2317
|
+
// The block is optional; the key inside it is not. `{ requiredCapabilities }`
|
|
2318
|
+
// with no `hosts` was accepted here and rejected at load with
|
|
2319
|
+
// `compatibility.hosts: Invalid input: expected array, received undefined`.
|
|
2320
|
+
if (rawHosts === undefined) {
|
|
2321
|
+
errors.push('compatibility.hosts is required');
|
|
2322
|
+
}
|
|
2323
|
+
else if (!Array.isArray(rawHosts)) {
|
|
2324
|
+
errors.push('compatibility.hosts must be an array');
|
|
2325
|
+
}
|
|
2326
|
+
const hosts = Array.isArray(rawHosts) ? rawHosts : [];
|
|
2327
|
+
if (Array.isArray(rawHosts) && hosts.length === 0) {
|
|
2328
|
+
errors.push('compatibility.hosts must name at least one host');
|
|
2329
|
+
}
|
|
2330
|
+
for (const host of hosts) {
|
|
2331
|
+
if (!DEVICE_HOST_SET.has(String(host))) {
|
|
2332
|
+
errors.push(`compatibility.hosts has unsupported host "${String(host)}" (one of ${exports.DEVICE_HOSTS_LIST})`);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
const rawCapabilities = definition.capabilities;
|
|
2337
|
+
if (rawCapabilities !== undefined && !Array.isArray(rawCapabilities)) {
|
|
2338
|
+
errors.push('capabilities must be an array');
|
|
2339
|
+
}
|
|
2340
|
+
const capabilities = new Set(Array.isArray(rawCapabilities) ? rawCapabilities.map(String) : []);
|
|
2341
|
+
if (Array.isArray(rawCapabilities) &&
|
|
2342
|
+
rawCapabilities.length > DEVICE_BUDGETS.maxCapabilities) {
|
|
2343
|
+
errors.push(`capabilities: ${rawCapabilities.length} entries exceeds budget ${DEVICE_BUDGETS.maxCapabilities}`);
|
|
2344
|
+
}
|
|
2345
|
+
for (const capability of capabilities) {
|
|
2346
|
+
if (!CAPABILITIES.has(capability)) {
|
|
2347
|
+
errors.push(`capability "${capability}" is unsupported`);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (components.some((component) => component.type === 'pointLight') &&
|
|
2351
|
+
!capabilities.has('presentation.light')) {
|
|
2352
|
+
errors.push('a point light requires capability presentation.light');
|
|
2353
|
+
}
|
|
2354
|
+
if (Object.values(object(definition.apps)).some((raw) => object(raw).type === 'canvas') &&
|
|
2355
|
+
!capabilities.has('app.canvas')) {
|
|
2356
|
+
errors.push('a canvas app requires capability app.canvas');
|
|
2357
|
+
}
|
|
2358
|
+
// The remainder of the host's `requiredByContent` list (`validate.ts:1496-1553`)
|
|
2359
|
+
// — the entries above do not cover. Each one is a thing the definition *uses*,
|
|
2360
|
+
// and the host refuses a definition that uses a surface it never declared, so
|
|
2361
|
+
// leaving any of these out is the audited bug class in its purest form: the
|
|
2362
|
+
// package is CLI-green and load-rejected.
|
|
2363
|
+
const requiresByContent = [
|
|
2364
|
+
[
|
|
2365
|
+
'state.durable',
|
|
2366
|
+
Object.values(state).some((spec) => object(spec).persist === true),
|
|
2367
|
+
],
|
|
2368
|
+
['presentation.nodeMotion', graphUsage.nodeMotion],
|
|
2369
|
+
[
|
|
2370
|
+
'clock.serverTime',
|
|
2371
|
+
components.some((component) => component.type === 'clockHands'),
|
|
2372
|
+
],
|
|
2373
|
+
['audio.playback', graphUsage.audioPlayback],
|
|
2374
|
+
['presentation.vfx', graphUsage.vfx],
|
|
2375
|
+
[
|
|
2376
|
+
'screen.render',
|
|
2377
|
+
components.some((component) => component.type === 'screen'),
|
|
2378
|
+
],
|
|
2379
|
+
[
|
|
2380
|
+
'screen.input',
|
|
2381
|
+
components.some((component) => component.type === 'screen' && component.interactive),
|
|
2382
|
+
],
|
|
2383
|
+
[
|
|
2384
|
+
'app.web.local',
|
|
2385
|
+
Object.values(apps).some((raw) => object(raw).type === 'localWeb'),
|
|
2386
|
+
],
|
|
2387
|
+
[
|
|
2388
|
+
'app.web.remote',
|
|
2389
|
+
Object.values(apps).some((raw) => ['remoteWeb', 'youtube'].includes(String(object(raw).type))),
|
|
2390
|
+
],
|
|
2391
|
+
[
|
|
2392
|
+
'app.web.panel',
|
|
2393
|
+
interactions.some((interaction) => interaction.presentation !== undefined),
|
|
2394
|
+
],
|
|
2395
|
+
['device.ports', ports.length > 0],
|
|
2396
|
+
];
|
|
2397
|
+
for (const [capability, used] of requiresByContent) {
|
|
2398
|
+
if (used && !capabilities.has(capability)) {
|
|
2399
|
+
errors.push(`capabilities: definition uses but does not declare ${capability}`);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
if (Object.values(object(definition.apps)).some((raw) => object(raw).type === 'remoteImage') &&
|
|
2403
|
+
!capabilities.has('app.image.remote')) {
|
|
2404
|
+
errors.push('a remote image app requires capability app.image.remote');
|
|
2405
|
+
}
|
|
2406
|
+
if (Object.values(object(definition.apps)).some((raw) => object(raw).type === 'ownerImage') &&
|
|
2407
|
+
!capabilities.has('app.image.owner')) {
|
|
2408
|
+
errors.push('an owner image app requires capability app.image.owner');
|
|
2409
|
+
}
|
|
2410
|
+
if (graphUsage.posture && !capabilities.has('character.posture'))
|
|
2411
|
+
errors.push('a character spot requires capability character.posture');
|
|
2412
|
+
if (graphUsage.animation && !capabilities.has('character.animate'))
|
|
2413
|
+
errors.push('character animation requires capability character.animate');
|
|
2414
|
+
if (graphUsage.hand && !capabilities.has('character.hand'))
|
|
2415
|
+
errors.push('hand attachment requires capability character.hand');
|
|
2416
|
+
// The host refuses any `collision` block on a Device that has not declared
|
|
2417
|
+
// `physics.solid` (`validate.ts` collision -> capability cross-check). Note
|
|
2418
|
+
// `model.glb` is deliberately NOT cross-checked here: the host has no such
|
|
2419
|
+
// rule, and enforcing it rejected valid packages.
|
|
2420
|
+
if (collision.length > 0 && !capabilities.has('physics.solid')) {
|
|
2421
|
+
errors.push('collision requires the physics.solid capability');
|
|
2422
|
+
}
|
|
2423
|
+
if (['serverValidated', 'serverAuthoritative'].includes(String(definition.authority)) &&
|
|
2424
|
+
!capabilities.has('room.authority')) {
|
|
2425
|
+
errors.push('server authority requires capability room.authority');
|
|
2426
|
+
}
|
|
2427
|
+
for (const required of Array.isArray(object(definition.compatibility).requiredCapabilities)
|
|
2428
|
+
? object(definition.compatibility).requiredCapabilities
|
|
2429
|
+
: []) {
|
|
2430
|
+
if (!capabilities.has(String(required))) {
|
|
2431
|
+
errors.push(`compatibility requires undeclared capability "${String(required)}"`);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
const migrationStarts = new Set();
|
|
2435
|
+
for (const raw of Array.isArray(definition.stateMigrations)
|
|
2436
|
+
? definition.stateMigrations
|
|
2437
|
+
: []) {
|
|
2438
|
+
const migration = object(raw);
|
|
2439
|
+
const from = Number(migration.from);
|
|
2440
|
+
const to = Number(migration.to);
|
|
2441
|
+
if (!Number.isInteger(from) || to !== from + 1) {
|
|
2442
|
+
errors.push(`state migration ${from}->${to} must advance by one`);
|
|
2443
|
+
}
|
|
2444
|
+
if (migrationStarts.has(from)) {
|
|
2445
|
+
errors.push(`state migrations duplicate from=${from}`);
|
|
2446
|
+
}
|
|
2447
|
+
migrationStarts.add(from);
|
|
2448
|
+
}
|
|
2449
|
+
if (definition.thumbnail !== undefined) {
|
|
2450
|
+
const thumbnail = object(definition.thumbnail);
|
|
2451
|
+
if (!THUMBNAIL_POLICIES.has(String(thumbnail.policy))) {
|
|
2452
|
+
errors.push(`thumbnail.policy must be one of ${exports.DEVICE_THUMBNAIL_POLICIES.join('|')}`);
|
|
2453
|
+
}
|
|
2454
|
+
const stateKeys = thumbnail.stateKeys;
|
|
2455
|
+
if (stateKeys !== undefined && !Array.isArray(stateKeys)) {
|
|
2456
|
+
errors.push('thumbnail.stateKeys must be an array');
|
|
2457
|
+
}
|
|
2458
|
+
const keys = Array.isArray(stateKeys) ? stateKeys : [];
|
|
2459
|
+
if (keys.length > DEVICE_BUDGETS.maxThumbnailStateKeys) {
|
|
2460
|
+
errors.push(`thumbnail.stateKeys: ${keys.length} entries exceeds budget ${DEVICE_BUDGETS.maxThumbnailStateKeys}`);
|
|
2461
|
+
}
|
|
2462
|
+
for (const key of keys) {
|
|
2463
|
+
if (!isNonEmptyName(key)) {
|
|
2464
|
+
errors.push('thumbnail.stateKeys entries must be non-empty names');
|
|
2465
|
+
}
|
|
2466
|
+
else if (!state[String(key)]) {
|
|
2467
|
+
errors.push(`thumbnail references unknown state "${String(key)}"`);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return {
|
|
2472
|
+
ok: errors.length === 0,
|
|
2473
|
+
directory: target.directory,
|
|
2474
|
+
definitionPath: target.path,
|
|
2475
|
+
modelPath,
|
|
2476
|
+
nodeNames,
|
|
2477
|
+
errors,
|
|
2478
|
+
warnings,
|
|
2479
|
+
definition: errors.length === 0 ? definition : undefined,
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
function testDeviceProject(input, mode) {
|
|
2483
|
+
const validation = validateDeviceProject(input);
|
|
2484
|
+
if (!validation.ok || !validation.definition) {
|
|
2485
|
+
return {
|
|
2486
|
+
ok: false,
|
|
2487
|
+
mode,
|
|
2488
|
+
clients: mode === 'multiplayer' ? 2 : 1,
|
|
2489
|
+
actionsExercised: [],
|
|
2490
|
+
finalState: {},
|
|
2491
|
+
persistentState: {},
|
|
2492
|
+
errors: validation.errors,
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
const definition = validation.definition;
|
|
2496
|
+
const authority = initialState(definition);
|
|
2497
|
+
const clientA = { ...authority };
|
|
2498
|
+
const clientB = { ...authority };
|
|
2499
|
+
const actions = Array.isArray(definition.interactions)
|
|
2500
|
+
? definition.interactions.map(object)
|
|
2501
|
+
: [];
|
|
2502
|
+
const exercised = [];
|
|
2503
|
+
const errors = [];
|
|
2504
|
+
for (const interaction of actions) {
|
|
2505
|
+
const value = exerciseValue(interaction, authority);
|
|
2506
|
+
invoke(definition, authority, interaction, value);
|
|
2507
|
+
Object.assign(clientA, authority);
|
|
2508
|
+
if (mode === 'multiplayer')
|
|
2509
|
+
Object.assign(clientB, authority);
|
|
2510
|
+
exercised.push(String(interaction.id));
|
|
2511
|
+
if (JSON.stringify(clientA) !== JSON.stringify(authority))
|
|
2512
|
+
errors.push(`client A diverged after ${String(interaction.id)}`);
|
|
2513
|
+
if (mode === 'multiplayer' &&
|
|
2514
|
+
JSON.stringify(clientB) !== JSON.stringify(authority))
|
|
2515
|
+
errors.push(`client B diverged after ${String(interaction.id)}`);
|
|
2516
|
+
}
|
|
2517
|
+
const stateSchema = object(definition.state);
|
|
2518
|
+
const persistentState = Object.fromEntries(Object.entries(authority).filter(([key]) => object(stateSchema[key]).persist === true));
|
|
2519
|
+
const remounted = initialState(definition);
|
|
2520
|
+
for (const [key, value] of Object.entries(persistentState))
|
|
2521
|
+
remounted[key] = coerce(object(stateSchema[key]), value);
|
|
2522
|
+
for (const [key, value] of Object.entries(persistentState)) {
|
|
2523
|
+
if (remounted[key] !== value)
|
|
2524
|
+
errors.push(`persistent state ${key} did not round-trip`);
|
|
2525
|
+
}
|
|
2526
|
+
return {
|
|
2527
|
+
ok: errors.length === 0,
|
|
2528
|
+
mode,
|
|
2529
|
+
clients: mode === 'multiplayer' ? 2 : 1,
|
|
2530
|
+
actionsExercised: exercised,
|
|
2531
|
+
finalState: authority,
|
|
2532
|
+
persistentState,
|
|
2533
|
+
errors,
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
function clockDefinition(id, title) {
|
|
2537
|
+
return {
|
|
2538
|
+
format: 'helix.device',
|
|
2539
|
+
formatVersion: 1,
|
|
2540
|
+
id,
|
|
2541
|
+
version: '1.0.0',
|
|
2542
|
+
title,
|
|
2543
|
+
model: {
|
|
2544
|
+
kind: 'glb',
|
|
2545
|
+
url: './model.glb',
|
|
2546
|
+
nodeBindings: {
|
|
2547
|
+
hourHand: 'hourHand',
|
|
2548
|
+
minuteHand: 'minuteHand',
|
|
2549
|
+
secondHand: 'secondHand',
|
|
2550
|
+
secondCounterweight: 'secondCounterweight',
|
|
2551
|
+
},
|
|
2552
|
+
},
|
|
2553
|
+
state: {},
|
|
2554
|
+
interactions: [],
|
|
2555
|
+
components: [
|
|
2556
|
+
{
|
|
2557
|
+
id: 'hands',
|
|
2558
|
+
type: 'clockHands',
|
|
2559
|
+
hourNode: 'hourHand',
|
|
2560
|
+
minuteNode: 'minuteHand',
|
|
2561
|
+
secondNode: 'secondHand',
|
|
2562
|
+
secondCounterweightNode: 'secondCounterweight',
|
|
2563
|
+
timeZone: 'local',
|
|
2564
|
+
},
|
|
2565
|
+
],
|
|
2566
|
+
graph: { triggers: [] },
|
|
2567
|
+
script: null,
|
|
2568
|
+
activationPreset: 'room',
|
|
2569
|
+
authority: 'presentationOnly',
|
|
2570
|
+
capabilities: ['model.glb', 'state.typed', 'clock.serverTime'],
|
|
2571
|
+
stateVersion: 1,
|
|
2572
|
+
compatibility: {
|
|
2573
|
+
hosts: ['home', 'world'],
|
|
2574
|
+
requiredCapabilities: ['model.glb', 'clock.serverTime'],
|
|
2575
|
+
},
|
|
2576
|
+
thumbnail: { policy: 'never' },
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
function deskDefinition(id, title) {
|
|
2580
|
+
return {
|
|
2581
|
+
format: 'helix.device',
|
|
2582
|
+
formatVersion: 1,
|
|
2583
|
+
id,
|
|
2584
|
+
version: '1.0.0',
|
|
2585
|
+
title,
|
|
2586
|
+
model: { kind: 'glb', url: './model.glb', nodeBindings: { top: 'top' } },
|
|
2587
|
+
state: {
|
|
2588
|
+
height: {
|
|
2589
|
+
type: 'float',
|
|
2590
|
+
min: 0.62,
|
|
2591
|
+
max: 1.25,
|
|
2592
|
+
default: 0.74,
|
|
2593
|
+
persist: true,
|
|
2594
|
+
},
|
|
2595
|
+
},
|
|
2596
|
+
interactions: [
|
|
2597
|
+
{
|
|
2598
|
+
id: 'setHeight',
|
|
2599
|
+
label: 'Adjust height',
|
|
2600
|
+
verb: 'use',
|
|
2601
|
+
control: {
|
|
2602
|
+
kind: 'number',
|
|
2603
|
+
stateKey: 'height',
|
|
2604
|
+
min: 0.62,
|
|
2605
|
+
max: 1.25,
|
|
2606
|
+
step: 0.01,
|
|
2607
|
+
unit: 'm',
|
|
2608
|
+
},
|
|
2609
|
+
},
|
|
2610
|
+
],
|
|
2611
|
+
components: [
|
|
2612
|
+
{
|
|
2613
|
+
id: 'topLift',
|
|
2614
|
+
type: 'slider',
|
|
2615
|
+
node: 'top',
|
|
2616
|
+
axis: 'y',
|
|
2617
|
+
stateKey: 'height',
|
|
2618
|
+
minOffsetM: 0.62,
|
|
2619
|
+
maxOffsetM: 1.25,
|
|
2620
|
+
durationMs: 900,
|
|
2621
|
+
easing: 'easeOutCubic',
|
|
2622
|
+
},
|
|
2623
|
+
],
|
|
2624
|
+
graph: { triggers: [] },
|
|
2625
|
+
script: null,
|
|
2626
|
+
activationPreset: 'room',
|
|
2627
|
+
authority: 'serverValidated',
|
|
2628
|
+
capabilities: [
|
|
2629
|
+
'model.glb',
|
|
2630
|
+
'state.typed',
|
|
2631
|
+
'state.durable',
|
|
2632
|
+
'presentation.nodeMotion',
|
|
2633
|
+
'room.authority',
|
|
2634
|
+
],
|
|
2635
|
+
stateVersion: 1,
|
|
2636
|
+
compatibility: {
|
|
2637
|
+
hosts: ['home', 'world'],
|
|
2638
|
+
requiredCapabilities: [
|
|
2639
|
+
'model.glb',
|
|
2640
|
+
'state.typed',
|
|
2641
|
+
'presentation.nodeMotion',
|
|
2642
|
+
'room.authority',
|
|
2643
|
+
],
|
|
2644
|
+
},
|
|
2645
|
+
thumbnail: { policy: 'discreteState', stateKeys: ['height'] },
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function televisionDefinition(id, title) {
|
|
2649
|
+
return {
|
|
2650
|
+
format: 'helix.device',
|
|
2651
|
+
formatVersion: 1,
|
|
2652
|
+
id,
|
|
2653
|
+
version: '1.0.0',
|
|
2654
|
+
title,
|
|
2655
|
+
model: { kind: 'glb', url: './model.glb', nodeBindings: { screen: 'screen' } },
|
|
2656
|
+
apps: {
|
|
2657
|
+
player: {
|
|
2658
|
+
type: 'youtube',
|
|
2659
|
+
entry: 'M7lc1UVf-VE',
|
|
2660
|
+
allowedOrigins: [
|
|
2661
|
+
'https://www.youtube.com',
|
|
2662
|
+
'https://www.youtube-nocookie.com',
|
|
2663
|
+
],
|
|
2664
|
+
permissions: ['web.content', 'network.fetch', 'audio.media'],
|
|
2665
|
+
},
|
|
2666
|
+
},
|
|
2667
|
+
state: {
|
|
2668
|
+
powered: { type: 'boolean', default: true, persist: true },
|
|
2669
|
+
video: { type: 'string', default: 'M7lc1UVf-VE', persist: true },
|
|
2670
|
+
playback: { type: 'enum', values: ['pause', 'play'], default: 'pause', persist: true },
|
|
2671
|
+
volume: { type: 'integer', min: 0, max: 100, default: 55, persist: true },
|
|
2672
|
+
},
|
|
2673
|
+
interactions: [
|
|
2674
|
+
{ id: 'power', label: 'Power', verb: 'use' },
|
|
2675
|
+
{
|
|
2676
|
+
id: 'queueVideo',
|
|
2677
|
+
label: 'Queue YouTube video',
|
|
2678
|
+
verb: 'use',
|
|
2679
|
+
control: { kind: 'text', stateKey: 'video', placeholder: 'YouTube URL or video ID', maxLength: 512, inputMode: 'url' },
|
|
2680
|
+
},
|
|
2681
|
+
{
|
|
2682
|
+
id: 'playback',
|
|
2683
|
+
label: 'Playback',
|
|
2684
|
+
verb: 'use',
|
|
2685
|
+
control: { kind: 'choice', stateKey: 'playback', options: [{ value: 'play', label: 'Play' }, { value: 'pause', label: 'Pause' }] },
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
id: 'volume',
|
|
2689
|
+
label: 'Volume',
|
|
2690
|
+
verb: 'use',
|
|
2691
|
+
control: { kind: 'number', stateKey: 'volume', min: 0, max: 100, step: 1, unit: '%' },
|
|
2692
|
+
},
|
|
2693
|
+
],
|
|
2694
|
+
components: [
|
|
2695
|
+
{
|
|
2696
|
+
id: 'display', type: 'screen', node: 'screen', app: 'player', widthM: 1.28,
|
|
2697
|
+
heightM: 0.72, interactive: true, powerStateKey: 'powered', sourceStateKey: 'video',
|
|
2698
|
+
playbackStateKey: 'playback', volumeStateKey: 'volume', suspendDistanceM: 24,
|
|
2699
|
+
},
|
|
2700
|
+
],
|
|
2701
|
+
ports: [
|
|
2702
|
+
{ id: 'power', direction: 'bidirectional', kind: 'value', dataType: 'boolean', interface: 'helix.power/1', semantic: 'power.state', authority: 'server', stateKey: 'powered', autoPair: true, maxLinks: 1 },
|
|
2703
|
+
{ id: 'volume', direction: 'bidirectional', kind: 'value', dataType: 'number', interface: 'helix.volume/1', semantic: 'media.volume', authority: 'server', stateKey: 'volume', autoPair: true, maxLinks: 4 },
|
|
2704
|
+
// The template's audio output is the port a creator links to a speaker
|
|
2705
|
+
// or an audio-reactive light, so it must be `routable`. It shipped as
|
|
2706
|
+
// `embedded-only`, which contradicted the sibling example
|
|
2707
|
+
// (`examples/continuum-media-television/device.json`) and the host's own
|
|
2708
|
+
// television fixture (`lib/devices/fixtures.ts`), and produced a link
|
|
2709
|
+
// that connected and then never carried audio.
|
|
2710
|
+
{ id: 'audio', direction: 'output', kind: 'stream', dataType: 'media.audio', interface: 'helix.audio/1', semantic: 'audio.main', authority: 'local', routability: 'routable', autoPair: true, maxLinks: 8 },
|
|
2711
|
+
],
|
|
2712
|
+
interfaces: ['helix.television/1', 'helix.media-player/1'],
|
|
2713
|
+
graph: { triggers: [{ on: 'interact', interaction: 'power', do: [{ step: 'toggleState', key: 'powered' }] }] },
|
|
2714
|
+
script: null,
|
|
2715
|
+
activationPreset: 'room',
|
|
2716
|
+
authority: 'serverValidated',
|
|
2717
|
+
capabilities: ['model.glb', 'state.typed', 'state.durable', 'room.authority', 'screen.render', 'screen.input', 'app.web.remote', 'audio.playback', 'device.ports'],
|
|
2718
|
+
stateVersion: 1,
|
|
2719
|
+
compatibility: { hosts: ['home', 'world'], requiredCapabilities: ['model.glb', 'state.typed', 'screen.render', 'screen.input', 'app.web.remote', 'audio.playback', 'device.ports', 'room.authority'] },
|
|
2720
|
+
thumbnail: { policy: 'discreteState', stateKeys: ['powered'] },
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
function createDeviceProject(input) {
|
|
2724
|
+
const directory = (0, node_path_1.resolve)(input.directory);
|
|
2725
|
+
const sourceModel = (0, node_path_1.resolve)(input.modelPath);
|
|
2726
|
+
if (!(0, node_fs_1.existsSync)(sourceModel))
|
|
2727
|
+
throw new Error(`Model does not exist: ${sourceModel}`);
|
|
2728
|
+
const nodeNames = readDeviceGlbNodeNames(sourceModel);
|
|
2729
|
+
(0, node_fs_1.mkdirSync)(directory, { recursive: true });
|
|
2730
|
+
const definitionPath = (0, node_path_1.join)(directory, exports.DEVICE_FILENAME);
|
|
2731
|
+
const modelPath = (0, node_path_1.join)(directory, 'model.glb');
|
|
2732
|
+
if ((0, node_fs_1.existsSync)(definitionPath) || (0, node_fs_1.existsSync)(modelPath)) {
|
|
2733
|
+
throw new Error(`Refusing to overwrite an existing Device project in ${directory}`);
|
|
2734
|
+
}
|
|
2735
|
+
const definition = input.template === 'clock'
|
|
2736
|
+
? clockDefinition(input.id, input.title)
|
|
2737
|
+
: input.template === 'television'
|
|
2738
|
+
? televisionDefinition(input.id, input.title)
|
|
2739
|
+
: deskDefinition(input.id, input.title);
|
|
2740
|
+
(0, node_fs_1.copyFileSync)(sourceModel, modelPath);
|
|
2741
|
+
(0, node_fs_1.writeFileSync)(definitionPath, `${JSON.stringify(definition, null, 2)}\n`);
|
|
2742
|
+
return { directory, definitionPath, modelPath, nodeNames };
|
|
2743
|
+
}
|
|
2744
|
+
//# sourceMappingURL=device.js.map
|