@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
|
@@ -0,0 +1,2002 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PER_RENDITION_ASSERT = exports.renderVehicle = exports.EVIDENCE_MAX = exports.VEHICLE_ROUTE_REVISION = void 0;
|
|
7
|
+
exports.wheelFailsGeometry = wheelFailsGeometry;
|
|
8
|
+
exports.tagRendition = tagRendition;
|
|
9
|
+
exports.artifactUrl = artifactUrl;
|
|
10
|
+
exports.unsealedRenditionErrors = unsealedRenditionErrors;
|
|
11
|
+
exports.renditionPlan = renditionPlan;
|
|
12
|
+
exports.waitPresented = waitPresented;
|
|
13
|
+
exports.buildWheelRows = buildWheelRows;
|
|
14
|
+
exports.bumperClearance = bumperClearance;
|
|
15
|
+
// Vehicle render route. Loads the item in the hosted test grounds through Try Now (`?tryItem=<id>`, a signed-in QA
|
|
16
|
+
// account), boards the driver's seat with the real E key, lets the car settle on its suspension, and then measures it
|
|
17
|
+
// from the live scene graph — the DRAWN geometry, never node origins — against the live physics tuning:
|
|
18
|
+
//
|
|
19
|
+
// vehicle.wheels.at_hubs drawn wheel centres vs physics hubs, drawn count vs physics count, no shared hubs
|
|
20
|
+
// vehicle.wheels.arch_clearance body poking into tyres, tyre top vs arch, ride height above the contact plane
|
|
21
|
+
// vehicle.wheels.spin_with_speed drawn wheel rotation vs distance / radius while rolling forward
|
|
22
|
+
// vehicle.steering.wheel_rotates front-wheel yaw and steering-wheel rotation at full lock
|
|
23
|
+
// vehicle.articulation.reaches_limits every declared panel to its hinge limit and back, attached nodes rigid
|
|
24
|
+
// vehicle.seats.addressable every declared seat in seat evidence, driver seat boardable
|
|
25
|
+
// + the four common asserts on the vehicle root
|
|
26
|
+
//
|
|
27
|
+
// Frames: six exterior views, four low square-on wheel close-ups, the driver's view, one per articulated panel at full
|
|
28
|
+
// open, steering at full lock (front wheel + steering wheel), and the car rolling at speed in the chase view.
|
|
29
|
+
// All camera poses are pinned in the BODY frame, so they show the car the same way whatever the spawn yaw.
|
|
30
|
+
const vehicleDriveAlive_1 = require("./vehicleDriveAlive");
|
|
31
|
+
const renditionManifest_1 = require("../vehicle/renditionManifest");
|
|
32
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
33
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
34
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
35
|
+
const browser_1 = require("./browser");
|
|
36
|
+
const vehiclePreview_1 = require("./vehiclePreview");
|
|
37
|
+
const vehicleStance_1 = require("./vehicleStance");
|
|
38
|
+
const commonAsserts_1 = require("./commonAsserts");
|
|
39
|
+
const resolve_1 = require("./resolve");
|
|
40
|
+
const vehicleOccupants_1 = require("./vehicleOccupants");
|
|
41
|
+
const vehiclePosture_1 = require("./vehiclePosture");
|
|
42
|
+
const vehicleGrip_1 = require("./vehicleGrip");
|
|
43
|
+
const vehicleInspection_1 = require("./vehicleInspection");
|
|
44
|
+
const evidenceOverlay_1 = require("./evidenceOverlay");
|
|
45
|
+
const vehiclePoses_1 = require("./vehiclePoses");
|
|
46
|
+
const wheelDesign_1 = require("../vehicle/wheelDesign");
|
|
47
|
+
const vehicleCabinFit_1 = require("./vehicleCabinFit");
|
|
48
|
+
const vehicleClearance_1 = require("./vehicleClearance");
|
|
49
|
+
const vehicleProbe_1 = require("./vehicleProbe");
|
|
50
|
+
const vehicleAddonProbe_1 = require("./vehicleAddonProbe");
|
|
51
|
+
const vehicleLamps_1 = require("./vehicleLamps");
|
|
52
|
+
const previewSeats_1 = require("../vehicle/previewSeats");
|
|
53
|
+
const previewAddon_1 = require("../vehicle/previewAddon");
|
|
54
|
+
const vehicleMath_1 = require("./vehicleMath");
|
|
55
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
56
|
+
exports.VEHICLE_ROUTE_REVISION = 'vehicle@2';
|
|
57
|
+
/** Default action keys per panel signal (the vehicle HUD's own assignments). Verified live by the durable state flip. */
|
|
58
|
+
const PANEL_KEYS = { doorFL: 'KeyO', doorFR: 'KeyP', doorRL: 'Semicolon', doorRR: 'Digit0', hood: 'Minus', trunk: 'Equal' };
|
|
59
|
+
const FALLBACK_KEYS = ['KeyO', 'KeyP', 'Semicolon', 'Digit0', 'Quote', 'Minus', 'Equal', 'KeyQ', 'KeyZ'];
|
|
60
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
61
|
+
const rd = (v, k = 1000) => (v === null || v === undefined || !Number.isFinite(v) ? null : Math.round(v * k) / k);
|
|
62
|
+
function declaredPanels(root) {
|
|
63
|
+
const panels = (root?.mesh?.articulation?.panels ?? []);
|
|
64
|
+
return panels.filter((p) => p?.id && p?.hinge && Array.isArray(p.nodes) && p.nodes.length).map((p) => ({
|
|
65
|
+
id: String(p.id), signal: String(p.signal ?? p.id), role: String(p.role ?? ''), nodes: p.nodes.map(String),
|
|
66
|
+
hinge: { axis: p.hinge.axis, closedDeg: Number(p.hinge.closedDeg ?? 0), openDeg: Number(p.hinge.openDeg), pivot: p.hinge.pivot },
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
function declaredSeats(root) {
|
|
70
|
+
const mod = root?.specialty?.module ?? {};
|
|
71
|
+
const seats = new Set(Object.keys(mod.seatVars ?? {}));
|
|
72
|
+
// The engine assumes the legacy key 'driver' when a manifest declares seatVars without naming its driver seat.
|
|
73
|
+
const driver = mod.driverSeat ? String(mod.driverSeat) : seats.has('driver') ? 'driver' : null;
|
|
74
|
+
if (driver)
|
|
75
|
+
seats.add(driver);
|
|
76
|
+
return { driver, seats: [...seats] };
|
|
77
|
+
}
|
|
78
|
+
async function vehiclePass(ctx, pass) {
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
const itemId = ctx.resolved.identity.itemId;
|
|
81
|
+
const url = `${ctx.webBase.replace(/\/$/, '')}/play/universal-item-test-grounds?tryItem=${encodeURIComponent(itemId)}`;
|
|
82
|
+
const errors = [];
|
|
83
|
+
const measureOnly = pass.measureOnly;
|
|
84
|
+
const frames = [];
|
|
85
|
+
const asserts = [];
|
|
86
|
+
const facts = { routeRevision: exports.VEHICLE_ROUTE_REVISION, thresholds: vehicleMath_1.VEHICLE_QA_THRESHOLDS, rendition: pass.rendition, tierThatSelectsIt: pass.tier };
|
|
87
|
+
const timings = {};
|
|
88
|
+
const mark = (k) => { timings[k] = +((Date.now() - started) / 1000).toFixed(1); };
|
|
89
|
+
let renderer = null;
|
|
90
|
+
let engineVersion = null;
|
|
91
|
+
const scriptUrls = [];
|
|
92
|
+
const panels = declaredPanels(ctx.resolved.root);
|
|
93
|
+
const seatsDeclared = declaredSeats(ctx.resolved.root);
|
|
94
|
+
// Debug only: HELIX_VQA_VEHICLE_ONLY=occupants skips the wheel / panel / steering / drive phases (a partial run is never a verdict).
|
|
95
|
+
const preview = ctx.preview;
|
|
96
|
+
const envOnly = (process.env.HELIX_VQA_VEHICLE_ONLY ?? '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
97
|
+
// A preview runs every phase but the occupants by default (`phases` names them explicitly); that is a chosen scope, not a partial debug run.
|
|
98
|
+
// `--addon`: the fitted add-ons are what is measured (no boarding, no stance, no drive); a baselineOnly pass reads the stock tuning.
|
|
99
|
+
const fittedAddOns = preview?.candidate.addOns ?? [];
|
|
100
|
+
const addOnMode = !!preview && (fittedAddOns.length > 0 || preview.baselineOnly === true);
|
|
101
|
+
// An add-on preview drives too: an add-on's audio delta over a car's audio block is what froze the R34 in Try Now.
|
|
102
|
+
const only = preview ? (addOnMode ? ['addons', 'drive'] : [...(preview.phases ?? ['exterior', 'poses', 'articulation', 'steering', 'cabin', 'drive']), ...(preview.paint ? ['paint'] : []), ...(preview.wheelFace ? ['wheelface'] : [])]) : envOnly;
|
|
103
|
+
const phase = (name) => (only.length === 0 || only.includes(name)) && !(measureOnly && MEASURE_ONLY_SKIPS.has(name));
|
|
104
|
+
if (!preview && envOnly.length)
|
|
105
|
+
errors.push(`HELIX_VQA_VEHICLE_ONLY=${envOnly.join(',')} was set: a partial debug run, not for submission`);
|
|
106
|
+
const previewSink = preview ? (0, vehiclePreview_1.newPreviewSink)() : null;
|
|
107
|
+
const occupantMeasures = [];
|
|
108
|
+
const occupantFailed = [];
|
|
109
|
+
const gripWindows = [];
|
|
110
|
+
const gripSeats = [];
|
|
111
|
+
const rigWarnings = [];
|
|
112
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(ctx.outDir, 'frames'), { recursive: true });
|
|
113
|
+
// geometry dumps of a non-default rendition go beside the default's, never over them
|
|
114
|
+
const dumpDir = measureOnly ? node_path_1.default.join(ctx.outDir, `rendition.${pass.rendition ?? 'x'}`) : ctx.outDir;
|
|
115
|
+
if (process.env.HELIX_VQA_DUMP_GEOMETRY)
|
|
116
|
+
node_fs_1.default.mkdirSync(dumpDir, { recursive: true });
|
|
117
|
+
const b = await (0, browser_1.launchVqaBrowser)({ storageState: ctx.storageState, executablePath: ctx.executablePath, width: 1440, height: 900 });
|
|
118
|
+
const { page } = b;
|
|
119
|
+
// A non-default rendition: the runtime asks for the model of the tier this machine resolves (the balanced one in a
|
|
120
|
+
// headless QA browser; measured 2026-10-03, a forced `low` Graphics setting does not reach the vehicle spawn), so it is
|
|
121
|
+
// handed that rendition's sealed bytes at that URL. The bytes are fetched here from the rendition's own artifact and
|
|
122
|
+
// must hash to its manifest CID; the pass then proves the page took them (`served`).
|
|
123
|
+
const sub = pass.substitute ?? null;
|
|
124
|
+
const subFacts = sub ? { from: sub.fromCid, to: sub.toCid, url: sub.url, served: 0 } : null;
|
|
125
|
+
if (sub && subFacts) {
|
|
126
|
+
facts.substitution = subFacts;
|
|
127
|
+
const fromHex = (0, resolve_1.hexOf)(sub.fromCid), toHex = (0, resolve_1.hexOf)(sub.toCid);
|
|
128
|
+
let bytes = null;
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch(sub.url, { headers: { 'user-agent': 'Mozilla/5.0 helix-visual-qa' } });
|
|
131
|
+
if (!res.ok)
|
|
132
|
+
throw new Error(`GET ${sub.url} -> ${res.status}`);
|
|
133
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
134
|
+
const got = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
|
|
135
|
+
subFacts.bytes = bytes.length;
|
|
136
|
+
subFacts.sha256 = got;
|
|
137
|
+
if (got !== toHex) {
|
|
138
|
+
errors.push(`rendition ${pass.rendition}: its artifact hashes to ${got.slice(0, 16)}…, not its manifest CID ${toHex?.slice(0, 16)}…`);
|
|
139
|
+
bytes = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
errors.push(`rendition ${pass.rendition}: could not fetch its sealed bytes (${String(e?.message ?? e).slice(0, 200)})`);
|
|
144
|
+
}
|
|
145
|
+
if (bytes && fromHex) {
|
|
146
|
+
const body = bytes;
|
|
147
|
+
await b.ctx.route((u) => u.href.includes(fromHex), async (route) => {
|
|
148
|
+
subFacts.served = Number(subFacts.served) + 1;
|
|
149
|
+
await route.fulfill({ status: 200, body, headers: { 'content-type': 'model/gltf-binary', 'cache-control': 'no-store', 'access-control-allow-origin': '*' } });
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Debug only: HELIX_VQA_DEBUG_ROUTES=<json file> [{ "match": "<url substring>", "file": "<local file>", "type": "<mime>" }]
|
|
154
|
+
// serves local bytes for matching requests (a candidate GLB in place of the pinned one, for calibration renders).
|
|
155
|
+
const debugRoutes = (process.env.HELIX_VQA_DEBUG_ROUTES ?? '').trim();
|
|
156
|
+
if (debugRoutes) {
|
|
157
|
+
errors.push(`HELIX_VQA_DEBUG_ROUTES=${debugRoutes} was set: a debug render with substituted bytes, not for submission`);
|
|
158
|
+
const rows = JSON.parse(node_fs_1.default.readFileSync(debugRoutes, 'utf8'));
|
|
159
|
+
for (const r of rows) {
|
|
160
|
+
const body = node_fs_1.default.readFileSync(r.file);
|
|
161
|
+
await b.ctx.route((u) => u.href.includes(r.match), (route) => route.fulfill({ status: 200, body, headers: { 'content-type': r.type ?? 'application/octet-stream', 'cache-control': 'no-store', 'access-control-allow-origin': '*' } }));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (process.env.HELIX_VQA_FAULT_AUDIO_NAN)
|
|
165
|
+
errors.push('HELIX_VQA_FAULT_AUDIO_NAN was set: NaN was injected into every audio write during the drive (a proof of the drive check, not for submission)');
|
|
166
|
+
const engineOverride = (process.env.HELIX_VQA_ENGINE_RUNTIME ?? '').trim();
|
|
167
|
+
if (engineOverride)
|
|
168
|
+
errors.push(`HELIX_VQA_ENGINE_RUNTIME=${engineOverride} was set: a debug render on another engine, not for submission`);
|
|
169
|
+
// A candidate engine BUILD (a local humanoid-character dist/index.js) served in place of the runtime the platform resolves: the
|
|
170
|
+
// pre-release proof of an engine change on the real page, before it is published. Never a submission.
|
|
171
|
+
const engineBundle = (process.env.HELIX_VQA_ENGINE_BUNDLE ?? '').trim();
|
|
172
|
+
if (engineBundle)
|
|
173
|
+
errors.push(`HELIX_VQA_ENGINE_BUNDLE=${engineBundle} was set: rendered on a local engine build, not for submission`);
|
|
174
|
+
// ── THE OCCUPANT IS THE PLATFORM'S REFERENCE AVATAR, NOT THE SUBMITTER'S ───────────────────────────────────────────
|
|
175
|
+
// Every seat is occupied by "this account's own avatar", and an account's avatar is whatever it wears. MEASURED on the
|
|
176
|
+
// 993 (engine 0.3.214, same build, same car): the QA accounts' default avatar seats with torso 20.5°, hands on the rim,
|
|
177
|
+
// crown +25 mm; the owner's account, wearing a hairstyle whose cards stand ~0.11 m over the skull, seats reclined to the
|
|
178
|
+
// rig's stop (35.2°), hands 131/91 mm off the rim, hair +6 mm under the lining — seven occupant FAILs for whoever
|
|
179
|
+
// happened to submit. A car's gate has to judge the car, and the cabin fit is sized for the platform default body, so
|
|
180
|
+
// the page is answered with the platform default avatar (`GET /universal-items/avatar/default`, public) for every seat.
|
|
181
|
+
// `HELIX_VQA_OWN_AVATAR=1` keeps the account's own avatar (how YOUR avatar fits this car; recorded, not for submission);
|
|
182
|
+
// `HELIX_VQA_EQUIPPED_AVATAR=<json>` states any composition (debug, to bisect an avatar defect).
|
|
183
|
+
const equippedOverride = (process.env.HELIX_VQA_EQUIPPED_AVATAR ?? '').trim();
|
|
184
|
+
const ownAvatar = /^(1|true|yes)$/i.test((process.env.HELIX_VQA_OWN_AVATAR ?? '').trim());
|
|
185
|
+
let occupantAvatar = { source: 'account', itemId: null };
|
|
186
|
+
if (equippedOverride)
|
|
187
|
+
errors.push(`HELIX_VQA_EQUIPPED_AVATAR=${equippedOverride} was set: the occupants wear a stated avatar, not the reference one (debug, not for submission)`);
|
|
188
|
+
else if (ownAvatar)
|
|
189
|
+
errors.push('HELIX_VQA_OWN_AVATAR=1 was set: the occupants wear this account\'s own avatar, not the platform reference (not for submission)');
|
|
190
|
+
facts.occupantAvatar = occupantAvatar;
|
|
191
|
+
if (!ownAvatar) {
|
|
192
|
+
let body = null;
|
|
193
|
+
if (equippedOverride)
|
|
194
|
+
body = node_fs_1.default.readFileSync(equippedOverride, 'utf8');
|
|
195
|
+
else {
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${ctx.apiBase.replace(/\/$/, '')}/api/v1/universal-items/avatar/default`, { headers: { accept: 'application/json' } });
|
|
198
|
+
if (res.ok)
|
|
199
|
+
body = await res.text();
|
|
200
|
+
else
|
|
201
|
+
errors.push(`occupants: the platform default avatar could not be read (HTTP ${res.status}); seats were filled with this account's avatar`);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
errors.push(`occupants: the platform default avatar could not be read (${String(e?.message ?? e).slice(0, 120)}); seats were filled with this account's avatar`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (body !== null) {
|
|
208
|
+
try {
|
|
209
|
+
const j = JSON.parse(body);
|
|
210
|
+
occupantAvatar = { source: equippedOverride ? 'stated' : 'platform-default', itemId: j.itemId ?? null };
|
|
211
|
+
facts.occupantAvatar = occupantAvatar;
|
|
212
|
+
}
|
|
213
|
+
catch { /* the page will say */ }
|
|
214
|
+
const answer = body;
|
|
215
|
+
await b.ctx.route(/\/universal-items\/avatar\/equipped\/me(\?|$)/, async (route) => {
|
|
216
|
+
if (route.request().method() !== 'GET') {
|
|
217
|
+
await route.continue();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const origin = route.request().headers().origin;
|
|
221
|
+
await route.fulfill({ status: 200, body: answer, contentType: 'application/json', headers: { 'cache-control': 'no-store', ...(origin ? { 'access-control-allow-origin': origin, 'access-control-allow-credentials': 'true', vary: 'Origin' } : { 'access-control-allow-origin': '*' }) } });
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (preview && previewSink) {
|
|
226
|
+
await (0, vehiclePreview_1.installVehiclePreviewRoutes)(b.ctx, preview.candidate, itemId, previewSink);
|
|
227
|
+
(0, vehiclePreview_1.watchProxyModelFetches)(b.ctx, previewSink);
|
|
228
|
+
}
|
|
229
|
+
// Content-addressed model bytes (`/content/sha256/<2>/<hex>`) carry no file extension, so the shared asset filter
|
|
230
|
+
// misses them; record them here so the byte-identity check below can see what was actually fetched.
|
|
231
|
+
const contentUrls = [];
|
|
232
|
+
b.ctx.on('request', (req) => {
|
|
233
|
+
const u = req.url();
|
|
234
|
+
if (/\/index\.js(\?|$)|\/runtime\//.test(u) && scriptUrls.length < 200)
|
|
235
|
+
scriptUrls.push(u);
|
|
236
|
+
if (/\/content\/sha256\/|\/sha256[:/][0-9a-f]{64}/.test(u) && !contentUrls.includes(u))
|
|
237
|
+
contentUrls.push(u);
|
|
238
|
+
});
|
|
239
|
+
b.ctx.on('response', (res) => {
|
|
240
|
+
const v = res.headers()['x-helix-resolved-version'];
|
|
241
|
+
if (v && /humanoid-character\/runtime\//.test(res.url()) && !engineVersion)
|
|
242
|
+
engineVersion = v;
|
|
243
|
+
});
|
|
244
|
+
// Retired-runtime shim (asset-lab/porsche-911-993/qa/lib.mjs): an avatar swap disposed the WORLD-OWNED interaction host,
|
|
245
|
+
// making boarding impossible. Neutralised for this browser only, and only if the line is still in the bundle.
|
|
246
|
+
const shim = { scanned: 0, patched: 0, authorityGate: 0 };
|
|
247
|
+
const shimUrl = /(\/packages\/[^/]+\/[^/]+\/code|\/instant-packages\/humanoid-character\/runtime\/[^/]+)\/(index|addons)\.js(\?.*)?$/;
|
|
248
|
+
await b.ctx.route(shimUrl, async (route) => {
|
|
249
|
+
try {
|
|
250
|
+
const reqUrl = route.request().url();
|
|
251
|
+
const target = engineOverride ? reqUrl.replace(/(\/instant-packages\/humanoid-character\/runtime\/)[^/]+\//, `$1${encodeURIComponent(engineOverride)}/`) : reqUrl;
|
|
252
|
+
if (!/\/index\.js(\?|$)/.test(reqUrl) && target === reqUrl) {
|
|
253
|
+
await route.continue();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const resp = await route.fetch(target === reqUrl ? undefined : { url: target });
|
|
257
|
+
let body = await resp.text();
|
|
258
|
+
shim.scanned++;
|
|
259
|
+
if (engineBundle && /\/instant-packages\/humanoid-character\/runtime\/[^/]+\/index\.js(\?|$)/.test(reqUrl)) {
|
|
260
|
+
body = node_fs_1.default.readFileSync(engineBundle, 'utf8');
|
|
261
|
+
engineVersion = /\bSYSTEM_VERSION\s*=\s*["']([^"']+)["']/.exec(body)?.[1] ?? engineVersion;
|
|
262
|
+
}
|
|
263
|
+
// the resolved version rides the 307 the redirect-following fetch consumed: read it off the bundle (`SYSTEM_VERSION`) instead
|
|
264
|
+
if (!engineVersion)
|
|
265
|
+
engineVersion = resp.headers()['x-helix-resolved-version'] ?? (/\bSYSTEM_VERSION\s*=\s*["']([^"']+)["']/.exec(body)?.[1] ?? null);
|
|
266
|
+
const needle = 'this.#options.interaction?.dispose?.();';
|
|
267
|
+
if (body.includes(needle)) {
|
|
268
|
+
body = body.split(needle).join('/* vqa-shim: interaction host is world-owned */');
|
|
269
|
+
shim.patched++;
|
|
270
|
+
}
|
|
271
|
+
// Preview only: the room reserved this car's entity from the PROXY's package, and the client refuses to draw a build
|
|
272
|
+
// whose digest differs from the room's (`authorityBuild !== requestedBuild` -> return, forever "still resolving").
|
|
273
|
+
// A candidate package necessarily differs, so the gate is lifted for this browser alone; the measurements are the
|
|
274
|
+
// client's own drawn geometry and controller tuning, which is what the candidate defines.
|
|
275
|
+
if (preview) {
|
|
276
|
+
const lifted = (0, vehiclePreview_1.liftAuthorityGate)(body);
|
|
277
|
+
if (lifted.lifted) {
|
|
278
|
+
body = lifted.body;
|
|
279
|
+
shim.authorityGate++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// `body` is the DECODED text: a kept `content-encoding: br` (the CDN compresses the runtime bundle) makes the browser brotli-decode plain
|
|
283
|
+
// JavaScript, the shell shows "Something went wrong" and no vehicle is ever presented.
|
|
284
|
+
const out = { ...resp.headers(), 'content-length': '', 'cache-control': 'no-store', 'access-control-allow-origin': '*' };
|
|
285
|
+
delete out['content-encoding'];
|
|
286
|
+
delete out['transfer-encoding'];
|
|
287
|
+
await route.fulfill({ response: resp, body, headers: out });
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
await route.continue().catch(() => undefined);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
// Debug only: HELIX_VQA_ENGINE_CORE=<version> serves that exact engine-core (the World's `@helix/engine-core/*` import
|
|
294
|
+
// map entries: audio, vehicle audio, ...) in place of the promoted one — e.g. to reproduce a fault an engine-core fix
|
|
295
|
+
// has since removed (the drive check's NaN-audio proof on 0.1.25). Recorded as a harness error: never a verdict.
|
|
296
|
+
const engineCorePin = (process.env.HELIX_VQA_ENGINE_CORE ?? '').trim();
|
|
297
|
+
if (engineCorePin) {
|
|
298
|
+
errors.push(`HELIX_VQA_ENGINE_CORE=${engineCorePin} was set: rendered on engine-core ${engineCorePin}, not the promoted one (debug, not for submission)`);
|
|
299
|
+
const probe = await fetch(`${ctx.apiBase.replace(/\/$/, '')}/api/v1/instant-packages/engine-core/runtime/${encodeURIComponent(engineCorePin)}/index.js`, { redirect: 'manual' });
|
|
300
|
+
const pinned = /\/packages\/([0-9a-f-]{36})\/([0-9a-f-]{36})\/code\//.exec(probe.headers.get('location') ?? '');
|
|
301
|
+
if (!pinned)
|
|
302
|
+
errors.push(`HELIX_VQA_ENGINE_CORE=${engineCorePin}: the platform resolves no such engine-core version`);
|
|
303
|
+
else {
|
|
304
|
+
const [, pkgId, versionId] = pinned;
|
|
305
|
+
const served = [];
|
|
306
|
+
const seen = [];
|
|
307
|
+
facts.engineCorePin = { version: engineCorePin, packageId: pkgId, versionId, served, seen };
|
|
308
|
+
b.ctx.on('request', (req) => { const u = req.url(); if ((u.includes(pkgId) || /engine-core/.test(u)) && seen.length < 40)
|
|
309
|
+
seen.push(u.slice(0, 200)); });
|
|
310
|
+
await b.ctx.route(new RegExp(`/packages/${pkgId}/(?!${versionId})[0-9a-f-]{36}/code/`), async (route) => {
|
|
311
|
+
const url = route.request().url().replace(new RegExp(`/packages/${pkgId}/[0-9a-f-]{36}/code/`), `/packages/${pkgId}/${versionId}/code/`);
|
|
312
|
+
if (served.length < 50)
|
|
313
|
+
served.push(url.slice(url.indexOf('/code/')));
|
|
314
|
+
const resp = await route.fetch({ url });
|
|
315
|
+
const body = await resp.body();
|
|
316
|
+
// the fetched body is DECODED: a kept content-encoding would make the browser decode it a second time
|
|
317
|
+
const out = { ...resp.headers(), 'cache-control': 'no-store', 'access-control-allow-origin': '*' };
|
|
318
|
+
delete out['content-encoding'];
|
|
319
|
+
delete out['transfer-encoding'];
|
|
320
|
+
delete out['content-length'];
|
|
321
|
+
await route.fulfill({ status: resp.status(), body, headers: out });
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// The site serves the platform runtime's addons through a cross-origin redirect that interception refuses (see browser.ts).
|
|
326
|
+
await (0, browser_1.installPlatformRuntimeCors)(b.ctx);
|
|
327
|
+
let frame = null;
|
|
328
|
+
let boarded = false;
|
|
329
|
+
const images = [];
|
|
330
|
+
const shot = async (id, label, group, settleMs = 450) => {
|
|
331
|
+
if (measureOnly)
|
|
332
|
+
return; // a non-default rendition is measured, never photographed for the critic
|
|
333
|
+
const file = node_path_1.default.join('frames', `${id}.jpg`);
|
|
334
|
+
await hideOverlays(page);
|
|
335
|
+
const dim = await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), settleMs);
|
|
336
|
+
frames.push({ id, label, group, file, width: dim.width, height: dim.height, subject: itemId });
|
|
337
|
+
images.push(await (0, commonAsserts_1.imageStats)(node_path_1.default.join(ctx.outDir, file)).catch(() => ({ file, lumaStdev: 0, meanLuma: 0 })));
|
|
338
|
+
};
|
|
339
|
+
// Evidence frames go INTO the bundle (and to the critic) in every pass, the measure-only ones too: a measured failure
|
|
340
|
+
// with no picture of it is one a reviewer cannot check. They are not counted by frame.non_empty (a cutaway is mostly sky).
|
|
341
|
+
const evidenceShot = async (id, label, group = 'evidence', overlay) => {
|
|
342
|
+
const file = node_path_1.default.join('frames', `${id}.jpg`);
|
|
343
|
+
await hideOverlays(page);
|
|
344
|
+
const dim = await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), 500);
|
|
345
|
+
if (overlay && frame) {
|
|
346
|
+
// the measured geometry, projected through the live camera, drawn over the capture (see evidenceOverlay.ts)
|
|
347
|
+
try {
|
|
348
|
+
const pts = (0, evidenceOverlay_1.overlayPoints)(overlay);
|
|
349
|
+
const pr = await frame.evaluate((flat) => globalThis.__vqaVeh.project(flat), pts);
|
|
350
|
+
const box = await (await frame.frameElement()).boundingBox().catch(() => null);
|
|
351
|
+
const vw = page.viewportSize()?.width ?? dim.width;
|
|
352
|
+
if (pr?.rect)
|
|
353
|
+
await (0, evidenceOverlay_1.annotateFrame)(node_path_1.default.join(ctx.outDir, file), overlay, { ndc: pr.ndc, rect: pr.rect, frameOffset: { x: box?.x ?? 0, y: box?.y ?? 0 }, scale: dim.width / vw });
|
|
354
|
+
else
|
|
355
|
+
errors.push(`evidence ${id}: the camera could not be read to draw the measured geometry`);
|
|
356
|
+
}
|
|
357
|
+
catch (e) {
|
|
358
|
+
errors.push(`evidence ${id}: drawing the measured geometry failed (${String(e?.message ?? e).slice(0, 160)})`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { id, label, group, file, width: dim.width, height: dim.height, subject: itemId };
|
|
362
|
+
};
|
|
363
|
+
const evidencePrefix = measureOnly && pass.rendition ? `evidence.${pass.rendition}.` : 'evidence.';
|
|
364
|
+
const ev = (fn) => frame.evaluate(fn);
|
|
365
|
+
const setCam = (c) => frame.evaluate((cc) => globalThis.__vqaVeh.setCam(cc), c);
|
|
366
|
+
const clearCam = () => frame.evaluate(() => globalThis.__vqaVeh.clearCam());
|
|
367
|
+
// results that asserts are built from (null = not measured)
|
|
368
|
+
let rows = null;
|
|
369
|
+
let physicsWheelCount = 0;
|
|
370
|
+
let duplicates = [];
|
|
371
|
+
let ride = null;
|
|
372
|
+
let stats = null;
|
|
373
|
+
const panelSweeps = [];
|
|
374
|
+
let lampJudgement = null;
|
|
375
|
+
let unoccupiedLamps = null;
|
|
376
|
+
let steering = null;
|
|
377
|
+
let spin = null;
|
|
378
|
+
let spinMode = 'keyboard';
|
|
379
|
+
let seatEvidence = null;
|
|
380
|
+
let bodyBounds = null;
|
|
381
|
+
let stanceMeasured = null;
|
|
382
|
+
let addOnProbe = null;
|
|
383
|
+
let addOnTuning = null;
|
|
384
|
+
try {
|
|
385
|
+
// ---------------------------------------------------------------------------------------------- load + identity
|
|
386
|
+
// One full re-load is allowed: a presentation that cannot be boarded is retried from navigation once before it
|
|
387
|
+
// is reported, and every attempt's diagnosis is kept in facts.attempts.
|
|
388
|
+
const attempts = [];
|
|
389
|
+
facts.attempts = attempts;
|
|
390
|
+
let board = { ok: false, reason: 'not attempted', tries: 0 };
|
|
391
|
+
const seatSends = [];
|
|
392
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
393
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
394
|
+
const presented = await waitPresented(page, 120_000, () => b.runtimeFaults[0] ? `the runtime refused to compose the car: ${b.runtimeFaults[0]}` : b.pageErrors[0] ? `the page raised an error before the car was presented: ${b.pageErrors[0]}` : null);
|
|
395
|
+
frame = presented.frame;
|
|
396
|
+
if (!frame) {
|
|
397
|
+
attempts.push({ attempt, presented: false, reason: presented.reason });
|
|
398
|
+
if (/refused/i.test(presented.reason) || attempt === 2)
|
|
399
|
+
throw new Error(`the vehicle was never presented in the test grounds: ${presented.reason}`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
mark(attempt === 1 ? 'presented' : 'presented2');
|
|
403
|
+
renderer = await (0, browser_1.rendererInfo)(page);
|
|
404
|
+
await frame.evaluate(vehicleProbe_1.INSTALL_VEHICLE_PROBE);
|
|
405
|
+
await (0, commonAsserts_1.installSceneStats)(frame);
|
|
406
|
+
await page.waitForTimeout(1500);
|
|
407
|
+
const evidence = await ev(`(() => { const v = globalThis.__assetGym.mp.portableVehicleEvidence().vehicles[0]; return v ? { itemId: v.itemId, package: v.package, seats: v.seats, compositionDigest: v.compositionDigest, authoritativeBuild: v.authoritativeBuild } : null; })()`);
|
|
408
|
+
facts.runtimeEvidence = evidence;
|
|
409
|
+
if (!evidence)
|
|
410
|
+
errors.push('portableVehicleEvidence() reported no vehicle');
|
|
411
|
+
else {
|
|
412
|
+
if (evidence.itemId !== itemId)
|
|
413
|
+
errors.push(`the runtime presented item ${evidence.itemId}, not ${itemId}`);
|
|
414
|
+
// A preview's package checksum is minted when the session is rewritten (it depends on the CDN origin the proxy serves from).
|
|
415
|
+
if (preview && previewSink?.sessions.length)
|
|
416
|
+
ctx.resolved.identity.vehicleChecksum = previewSink.sessions[previewSink.sessions.length - 1].candidateChecksum;
|
|
417
|
+
const want = ctx.resolved.identity.vehicleChecksum;
|
|
418
|
+
const got = evidence.package?.checksumSha256 ?? null;
|
|
419
|
+
facts.packageIdentity = { pinnedVersion: ctx.resolved.identity.packageVersion, pinnedChecksum: want, runtimeRevision: evidence.package?.revision ?? null, runtimeChecksum: got, match: !!want && want === got };
|
|
420
|
+
if (!want)
|
|
421
|
+
errors.push('the item has no properties.vehiclePackageIdentity.checksumSha256 to bind the render to');
|
|
422
|
+
else if (want !== got)
|
|
423
|
+
errors.push(`the runtime served vehicle package checksum ${got ?? 'none'} (revision ${evidence.package?.revision ?? '?'}), not the pinned ${want} (${ctx.resolved.identity.packageVersion})`);
|
|
424
|
+
}
|
|
425
|
+
await page.mouse.click(720, 450).catch(() => undefined);
|
|
426
|
+
// ---------------------------------------------------------------------------------------------- board + settle
|
|
427
|
+
if (addOnMode) {
|
|
428
|
+
board = { ok: false, reason: 'not attempted (add-on preview measures the fitted parts, it does not drive)', tries: 0 };
|
|
429
|
+
attempts.push({ attempt, presented: true, board: false, reason: board.reason });
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
// A preview boards with the CANDIDATE's own seat ids: the room only knows the stand-in's, so the seat request the client makes in the
|
|
433
|
+
// candidate's vocabulary is translated into the stand-in's at the room send (see vehicle/previewSeats.ts).
|
|
434
|
+
if (preview && previewSink?.sessions.length) {
|
|
435
|
+
const seatTable = previewSink.sessions[previewSink.sessions.length - 1]?.seats?.table;
|
|
436
|
+
const installed = seatTable ? await frame.evaluate(`(${previewSeats_1.INSTALL_SEAT_BRIDGE})(${JSON.stringify(seatTable)})`).catch((e) => ({ ok: false, why: String(e?.message ?? e).slice(0, 200) })) : { ok: false, why: 'no seat table (published car, or no session was rewritten)' };
|
|
437
|
+
facts.seatBridge = { installed, report: previewSink.sessions[previewSink.sessions.length - 1]?.seats ?? null, log: seatSends };
|
|
438
|
+
}
|
|
439
|
+
// the lamps with NOBODY in the car (parked, unoccupied): the other half of the brake-at-standstill decision
|
|
440
|
+
if (!addOnMode && phase('articulation')) {
|
|
441
|
+
try {
|
|
442
|
+
const lp = (0, vehicleLamps_1.planLamps)(ctx.resolved.root);
|
|
443
|
+
if (lp.functions.length > 0 && (await frame.evaluate(vehicleLamps_1.INSTALL_LAMP_PROBE)))
|
|
444
|
+
unoccupiedLamps = await frame.evaluate((slots) => globalThis.__vqaLamps.snapshot(slots), lp.slots);
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
unoccupiedLamps = null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
board = await boardDriver(page, frame, seatsDeclared.driver);
|
|
451
|
+
// the seat sends the client made this load, as the bridge saw them (a reload starts a new page, so read them per attempt)
|
|
452
|
+
const seatLog = preview && facts.seatBridge ? await frame.evaluate(() => globalThis.__vqaSeatLog ?? null).catch(() => null) : null;
|
|
453
|
+
if (seatLog)
|
|
454
|
+
seatSends.push(...seatLog.map((x) => ({ attempt, ...x })));
|
|
455
|
+
if (facts.seatBridge)
|
|
456
|
+
facts.seatBridge.log = seatSends;
|
|
457
|
+
attempts.push({ attempt, presented: true, board: board.ok, reason: board.reason, trace: board.trace, ...(seatLog ? { seatSends: seatLog.length } : {}) });
|
|
458
|
+
if (board.ok)
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
if (!frame)
|
|
462
|
+
throw new Error('no world frame');
|
|
463
|
+
boarded = board.ok;
|
|
464
|
+
facts.board = board;
|
|
465
|
+
mark('boarded');
|
|
466
|
+
if (!boarded && !addOnMode)
|
|
467
|
+
errors.push(`could not board the driver seat after a full reload (${board.reason})`);
|
|
468
|
+
// An add-on preview does not drive: the parked car's wheel contacts never report grounded, and the fitted parts hang off the same nodes
|
|
469
|
+
// the hubs are read from, so a short pause (the models are fetched after the car spawns) replaces the settle.
|
|
470
|
+
const settle = addOnMode ? (await sleep(1500), { grounded: true, still: true, seconds: 1.5, last: null }) : await waitSettled(frame, boarded ? 9000 : 3000);
|
|
471
|
+
facts.settle = settle;
|
|
472
|
+
if (!settle.grounded)
|
|
473
|
+
errors.push(`the car never settled on all wheels (${JSON.stringify(settle.last)})`);
|
|
474
|
+
mark('settled');
|
|
475
|
+
// ---------------------------------------------------------------------------------------------- fitted add-ons
|
|
476
|
+
if (addOnMode) {
|
|
477
|
+
await frame.evaluate(vehicleAddonProbe_1.INSTALL_ADDON_PROBE);
|
|
478
|
+
// the engine mounts add-on models after the car spawns (it fetches their GLBs): wait until every fitted model is drawn, bounded
|
|
479
|
+
const wantIds = fittedAddOns.filter((a) => a.kind !== 'tune').map((a) => a.itemId);
|
|
480
|
+
const bindNames = fittedAddOns.flatMap((a) => { const b = a.frame?.bind; return typeof b?.root === 'string' ? [b.root] : []; });
|
|
481
|
+
const t0 = Date.now();
|
|
482
|
+
let read = { attach: [], hubs: [], rimNodes: [], carBounds: null };
|
|
483
|
+
for (;;) {
|
|
484
|
+
read = await frame.evaluate(({ ids, names }) => globalThis.__vqaAddon.read(ids, names), { ids: wantIds, names: bindNames });
|
|
485
|
+
const have = new Set(read.attach.map((a) => a.itemId));
|
|
486
|
+
if (wantIds.every((id) => have.has(id)) || Date.now() - t0 > 20_000)
|
|
487
|
+
break;
|
|
488
|
+
await sleep(500);
|
|
489
|
+
}
|
|
490
|
+
await sleep(800); // let the suspension finish moving the wheels the add-on hangs off
|
|
491
|
+
read = await frame.evaluate(({ ids, names }) => globalThis.__vqaAddon.read(ids, names), { ids: wantIds, names: bindNames });
|
|
492
|
+
const tuning = await frame.evaluate(() => globalThis.__vqaAddon.tuning());
|
|
493
|
+
facts.addOn = { probe: read, tuning: preview?.baselineOnly ? tuning : { leaves: Object.keys(tuning).length }, waitedMs: Date.now() - t0 };
|
|
494
|
+
addOnTuning = tuning;
|
|
495
|
+
addOnProbe = read;
|
|
496
|
+
if (!preview?.baselineOnly)
|
|
497
|
+
stats = await ev('globalThis.__vqaVeh.stats()');
|
|
498
|
+
mark('addon-measured');
|
|
499
|
+
if (!preview?.baselineOnly) {
|
|
500
|
+
const car = read.carBounds;
|
|
501
|
+
const allMin = car ? [...car.min] : [-1, 0, -2.5];
|
|
502
|
+
const allMax = car ? [...car.max] : [1, 1.5, 2.5];
|
|
503
|
+
for (const a of read.attach) {
|
|
504
|
+
if (a.min && a.max)
|
|
505
|
+
for (let i = 0; i < 3; i++) {
|
|
506
|
+
allMin[i] = Math.min(allMin[i], a.min[i]);
|
|
507
|
+
allMax[i] = Math.max(allMax[i], a.max[i]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const groundY = Math.max(allMin[1], -1.2);
|
|
511
|
+
const carMin = [allMin[0], groundY, allMin[2]];
|
|
512
|
+
await setCam((0, vehicleMath_1.camFit)(carMin, allMax, [0.85, 0.35, 1], 30, 1.6, 0.86));
|
|
513
|
+
await shot('addon.front34', 'Fitted: front three-quarter (left)', 'exterior', 900);
|
|
514
|
+
await setCam((0, vehicleMath_1.camFit)(carMin, allMax, [-0.85, 0.35, -1], 30, 1.6, 0.86));
|
|
515
|
+
await shot('addon.rear34', 'Fitted: rear three-quarter (right)', 'exterior', 450);
|
|
516
|
+
const centre = car ? [(car.min[0] + car.max[0]) / 2, (car.min[1] + car.max[1]) / 2, (car.min[2] + car.max[2]) / 2] : [0, 0.5, 0];
|
|
517
|
+
for (const [index, a] of fittedAddOns.entries()) {
|
|
518
|
+
const copies = read.attach.filter((x) => x.itemId === a.itemId && x.centre);
|
|
519
|
+
if (a.kind === 'wheel') {
|
|
520
|
+
for (const c of copies) {
|
|
521
|
+
const [cx, cy, cz] = c.centre;
|
|
522
|
+
const out = Math.sign(cx) || 1;
|
|
523
|
+
const rad = c.min && c.max ? Math.max(c.max[1] - c.min[1], c.max[2] - c.min[2]) / 2 : 0.33;
|
|
524
|
+
const dist = Math.max(1.5, rad * 6.2);
|
|
525
|
+
await setCam({ pos: [cx + out * dist, cy + 0.06, cz], target: [cx, cy, cz], fov: 34, near: 0.05 });
|
|
526
|
+
await shot(`addon.${index}.${(c.socketNode ?? c.name).replace(/[^a-z0-9]+/gi, '_')}`, `Add-on ${index + 1} ${a.title.slice(0, 24)} — ${c.socketNode ?? 'wheel'} square-on`, 'addons');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else if (copies.length) {
|
|
530
|
+
const c = copies[0];
|
|
531
|
+
const [cx, cy, cz] = c.centre;
|
|
532
|
+
const ext = c.min && c.max ? Math.max(c.max[0] - c.min[0], c.max[1] - c.min[1], c.max[2] - c.min[2]) : 0.5;
|
|
533
|
+
let dx = cx - centre[0], dz = cz - centre[2];
|
|
534
|
+
const dl = Math.hypot(dx, dz);
|
|
535
|
+
if (dl < 0.2) {
|
|
536
|
+
dx = -0.7;
|
|
537
|
+
dz = -0.7;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
dx /= dl;
|
|
541
|
+
dz /= dl;
|
|
542
|
+
}
|
|
543
|
+
const dist = Math.max(0.9, ext * 1.9);
|
|
544
|
+
await setCam({ pos: [cx + dx * dist, cy + dist * 0.4, cz + dz * dist], target: [cx, cy, cz], fov: 34, near: 0.05 });
|
|
545
|
+
await shot(`addon.${index}.close`, `Add-on ${index + 1} ${a.title.slice(0, 28)} — close-up`, 'addons');
|
|
546
|
+
}
|
|
547
|
+
else if (a.kind === 'tune') {
|
|
548
|
+
await setCam((0, vehicleMath_1.camFit)(carMin, allMax, [1, 0.3, 0.2], 30, 1.6, 0.86));
|
|
549
|
+
await shot(`addon.${index}.car`, `Add-on ${index + 1} ${a.title.slice(0, 28)} (tuning only, nothing drawn)`, 'addons');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
await clearCam();
|
|
553
|
+
}
|
|
554
|
+
mark('addon-shots');
|
|
555
|
+
// An add-on preview DRIVES too, as the seated driver with the World's audio running: an add-on's audio delta over a
|
|
556
|
+
// car's audio block that lacked engineGainDb/exhaustGainDb made a NaN that froze the R34 in Try Now — a fault only
|
|
557
|
+
// visible with the add-on fitted, so only this run can catch it (vehicle.drive.responds_with_audio).
|
|
558
|
+
if (phase('drive') && !preview?.baselineOnly) {
|
|
559
|
+
try {
|
|
560
|
+
const driver = await boardDriver(page, frame, seatsDeclared.driver);
|
|
561
|
+
facts.addOnDriveBoard = { ok: driver.ok, reason: driver.reason, tries: driver.tries };
|
|
562
|
+
if (!driver.ok) {
|
|
563
|
+
asserts.push({ id: 'vehicle.drive.responds_with_audio', pass: false, severity: 'block', subject: itemId, detail: `could not board the driver to drive with the add-on(s) fitted (${driver.reason})` });
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
await waitSettled(frame, 9000);
|
|
567
|
+
const driveRows = buildWheelRows(await ev('globalThis.__vqaVeh.pull()')).rows;
|
|
568
|
+
const driveBounds = await ev('globalThis.__vqaVeh.bounds()');
|
|
569
|
+
const d = await driveAndSample(page, frame, driveRows, shot, clearCam, driveBounds);
|
|
570
|
+
const alive = (0, vehicleDriveAlive_1.judgeDriveAlive)(d.alive);
|
|
571
|
+
facts.drive = d.facts;
|
|
572
|
+
facts.driveAlive = alive.measured;
|
|
573
|
+
asserts.push({ id: 'vehicle.drive.responds_with_audio', pass: alive.pass, severity: 'block', subject: itemId, detail: `${alive.detail} (add-on(s) fitted)`, measured: alive.measured });
|
|
574
|
+
if (alive.harnessError)
|
|
575
|
+
errors.push(alive.harnessError);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (e) {
|
|
579
|
+
const why = String(e?.message ?? e).slice(0, 200);
|
|
580
|
+
errors.push(`drive with the add-on(s) fitted: ${why}`);
|
|
581
|
+
if (!asserts.some((a) => a.id === 'vehicle.drive.responds_with_audio'))
|
|
582
|
+
asserts.push({ id: 'vehicle.drive.responds_with_audio', pass: false, severity: 'block', subject: itemId, detail: `the drive could not complete with the add-on(s) fitted: ${why}` });
|
|
583
|
+
}
|
|
584
|
+
mark('drive');
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
// ---------------------------------------------------------------------------------------------- wheel geometry
|
|
589
|
+
const pull = await ev('globalThis.__vqaVeh.pull()');
|
|
590
|
+
// the drawn wheels follow the instantaneous suspension, and a car rocks for seconds after the driver gets in:
|
|
591
|
+
// judge the wheels at the MEDIAN compression over a window, not at whatever instant the pull happened to catch
|
|
592
|
+
const window = await frame.evaluate((ms) => globalThis.__vqaVeh.compressionWindow(ms), 1500);
|
|
593
|
+
pull.compressionWindow = window.map((xs) => { const s = [...xs].sort((a, b) => a - b); return { n: s.length, min: s[0] ?? null, median: s.length ? s[Math.floor(s.length / 2)] : null, max: s[s.length - 1] ?? null }; });
|
|
594
|
+
physicsWheelCount = pull.wheels.length;
|
|
595
|
+
// raw geometry for offline re-analysis / threshold calibration (vehicle.spec.ts fixtures)
|
|
596
|
+
if (process.env.HELIX_VQA_DUMP_GEOMETRY)
|
|
597
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(dumpDir, 'geometry.json'), JSON.stringify(pull));
|
|
598
|
+
facts.batching = { batches: pull.batches, proxies: pull.proxies, bodyMeshes: pull.bodyMeshes, bodyVerts: pull.bodyVerts, excludedHardware: pull.excludedHardware };
|
|
599
|
+
const built = buildWheelRows(pull);
|
|
600
|
+
rows = built.rows;
|
|
601
|
+
duplicates = built.duplicates;
|
|
602
|
+
ride = built.ride;
|
|
603
|
+
facts.wheels = rows;
|
|
604
|
+
facts.rideHeight = ride;
|
|
605
|
+
facts.contactPlane = built.plane;
|
|
606
|
+
facts.strayWheelMeshes = pull.strays;
|
|
607
|
+
if (preview && rows) {
|
|
608
|
+
const m = (0, vehicleStance_1.measureStance)(rows, built.plane, ride, { physicsSettled: settle.grounded });
|
|
609
|
+
stanceMeasured = m;
|
|
610
|
+
facts.stance = { declared: (0, vehicleStance_1.declaredStance)(ctx.resolved.root), measured: m, settledBodyOriginWorld: settle.last?.p ?? null };
|
|
611
|
+
}
|
|
612
|
+
if (!physicsWheelCount)
|
|
613
|
+
errors.push('the live tuning has no wheel contacts');
|
|
614
|
+
stats = await ev('globalThis.__vqaVeh.stats()');
|
|
615
|
+
facts.drawnTriangles = stats?.triangles ?? null;
|
|
616
|
+
bodyBounds = await ev('globalThis.__vqaVeh.bounds()');
|
|
617
|
+
facts.bodyBounds = bodyBounds;
|
|
618
|
+
mark('measured');
|
|
619
|
+
// ---------------------------------------------------------------------------------------------- exterior + wheels
|
|
620
|
+
const groundY = built.plane[0];
|
|
621
|
+
const bb = bodyBounds;
|
|
622
|
+
const carMin = [bb.min[0], Math.max(bb.min[1], groundY), bb.min[2]];
|
|
623
|
+
const views = [
|
|
624
|
+
['ext.front', 'Front', [0, 0.25, 1]],
|
|
625
|
+
['ext.rear', 'Rear', [0, 0.25, -1]],
|
|
626
|
+
['ext.left', 'Left side', [1, 0.12, 0]],
|
|
627
|
+
['ext.right', 'Right side', [-1, 0.12, 0]],
|
|
628
|
+
['ext.front34', 'Front three-quarter (left)', [0.85, 0.35, 1]],
|
|
629
|
+
['ext.rear34', 'Rear three-quarter (right)', [-0.85, 0.35, -1]],
|
|
630
|
+
];
|
|
631
|
+
if (phase('exterior'))
|
|
632
|
+
for (const [id, label, dir] of views) {
|
|
633
|
+
await setCam((0, vehicleMath_1.camFit)(carMin, bb.max, dir, 30, 1.6, 0.86));
|
|
634
|
+
await shot(id, label, 'exterior', id === 'ext.front' ? 900 : 450);
|
|
635
|
+
}
|
|
636
|
+
if (measureOnly) {
|
|
637
|
+
// one small diagnostic frame (kept beside the bundle, NOT in bundle.frames, so no critic ever sees it): proof the
|
|
638
|
+
// rendition drew something, and the input of frame.non_empty for this rendition
|
|
639
|
+
await setCam((0, vehicleMath_1.camFit)(carMin, bb.max, [0.85, 0.35, 1], 30, 1.6, 0.86));
|
|
640
|
+
await hideOverlays(page);
|
|
641
|
+
const file = node_path_1.default.join('frames', `rendition.${pass.rendition ?? 'x'}.front34.jpg`);
|
|
642
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), 900);
|
|
643
|
+
images.push(await (0, commonAsserts_1.imageStats)(node_path_1.default.join(ctx.outDir, file)).catch(() => ({ file, lumaStdev: 0, meanLuma: 0 })));
|
|
644
|
+
facts.diagnosticFrame = file;
|
|
645
|
+
// a wheel this rendition draws wrong gets its own in-bundle frame, low and square-on like the default pass's
|
|
646
|
+
if (rows) {
|
|
647
|
+
const shots = [];
|
|
648
|
+
for (const r of rows) {
|
|
649
|
+
if (!wheelFailsGeometry(r))
|
|
650
|
+
continue;
|
|
651
|
+
const c = r.drawnCentre ?? r.hub;
|
|
652
|
+
const out = Math.sign(c[0]) || 1;
|
|
653
|
+
const rad = r.radius ?? r.physicsRadius;
|
|
654
|
+
// closer than the default pass's wheel frame, on the tyre's upper half and the arch over it, where the
|
|
655
|
+
// overlap is: a 5 cm intrusion has to be more than a few pixels
|
|
656
|
+
await setCam({ pos: [c[0] + out * Math.max(1.0, rad * 3.6), c[1] + rad * 0.55, c[2]], target: [c[0], c[1] + rad * 0.55, c[2]], fov: 34, near: 0.05 });
|
|
657
|
+
const sfx = r.id.replace(/^wheel_/, '');
|
|
658
|
+
const id = `evidence.${pass.rendition}.wheel.${sfx}`;
|
|
659
|
+
// the cross-section at the wheel's centre plane, from the same pull the arch was measured on
|
|
660
|
+
const pw = pull.wheels.find((w) => w.id === r.id);
|
|
661
|
+
const g = pw?.axleNode ? (0, vehicleMath_1.wheelGeometry)(pw.points, pw.axleNode) : null;
|
|
662
|
+
const sec = g ? (0, evidenceOverlay_1.wheelSectionOverlay)(g.centre, g.axle, g.radius, pull.bodyTris.tris, vehicleMath_1.VEHICLE_QA_THRESHOLDS.hubZoneFraction, {
|
|
663
|
+
tyre: `tyre ${sfx.toUpperCase()}, cut through (r ${(g.radius * 100).toFixed(1)} cm)`,
|
|
664
|
+
body: 'body, cut where it reaches deepest',
|
|
665
|
+
overlap: `body inside the tyre (arch gap ${r.archGapM === null ? '?' : (r.archGapM * 100).toFixed(1)} cm)`,
|
|
666
|
+
}, g.width) : null;
|
|
667
|
+
frames.push(await evidenceShot(id, `Evidence: wheel ${sfx.toUpperCase()} on the ${pass.rendition} rendition, low and square-on, with the measured cross-section drawn over it: white = the tyre, yellow = the body cut across the tread, red = body inside the tyre. Measured arch gap ${r.archGapM === null ? '?' : (r.archGapM * 100).toFixed(1)} cm${r.penetrationCount ? `, body ${((r.maxPenetrationM ?? 0) * 100).toFixed(1)} cm inside the tyre` : ''}.`, 'evidence', sec?.overlay));
|
|
668
|
+
shots.push(id);
|
|
669
|
+
}
|
|
670
|
+
facts.wheelEvidence = shots;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (rows && phase('exterior')) {
|
|
674
|
+
for (const r of rows) {
|
|
675
|
+
const c = r.drawnCentre ?? r.hub;
|
|
676
|
+
const out = Math.sign(c[0]) || 1;
|
|
677
|
+
const rad = r.radius ?? r.physicsRadius;
|
|
678
|
+
const dist = Math.max(1.5, rad * 6.2);
|
|
679
|
+
await setCam({ pos: [c[0] + out * dist, c[1] + 0.06, c[2]], target: [c[0], c[1] + rad * 0.3, c[2]], fov: 34, near: 0.05 });
|
|
680
|
+
const sfx = r.id.replace(/^wheel_/, '');
|
|
681
|
+
await shot(`wheel.${sfx}`, `Wheel ${sfx.toUpperCase()} — low, square-on`, 'wheels');
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
mark('exterior');
|
|
685
|
+
// ---------------------------------------------------------------------------------------------- appearance poses
|
|
686
|
+
// Preview only: the fixed, named views an author compares with the reference photos (vehiclePoses.ts). Written beside
|
|
687
|
+
// the bundle under poses/, never into bundle.frames (they are not judged, and frame.non_empty does not count them).
|
|
688
|
+
if (preview && !addOnMode && phase('poses')) {
|
|
689
|
+
const poseFacts = [];
|
|
690
|
+
facts.poses = poseFacts;
|
|
691
|
+
try {
|
|
692
|
+
const driver = await frame.evaluate(() => {
|
|
693
|
+
try {
|
|
694
|
+
const c = globalThis.__vqaVeh.e().interior?.cabin;
|
|
695
|
+
if (!c)
|
|
696
|
+
return null;
|
|
697
|
+
const s = c.seats.find((x) => x.role === 'driver') ?? c.seats[0];
|
|
698
|
+
if (!s)
|
|
699
|
+
return null;
|
|
700
|
+
return { hip: [s.hipPoint[0], s.hipPoint[1], s.hipPoint[2]], rim: c.steering?.centre ? [c.steering.centre[0], c.steering.centre[1], c.steering.centre[2]] : null };
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
const plans = (0, vehiclePoses_1.planPoses)({ min: carMin, max: bb.max }, driver);
|
|
707
|
+
const hid = await frame.evaluate(() => globalThis.__vqaVeh.backdrop(true));
|
|
708
|
+
// every lamp and every local light off for the poses (a hotspot or bloom over the nose hides its shape); the lit
|
|
709
|
+
// frames elsewhere keep the lamps and the world's lights as the runtime drives them
|
|
710
|
+
const lightsOff = await frame.evaluate(() => globalThis.__vqaVeh.lightsOff(true)).catch((e) => ({ error: String(e?.message ?? e).slice(0, 160) }));
|
|
711
|
+
facts.posesLightsOff = lightsOff;
|
|
712
|
+
try {
|
|
713
|
+
for (const pl of plans) {
|
|
714
|
+
if (!pl.cam) {
|
|
715
|
+
poseFacts.push({ id: pl.id, label: pl.label, file: null, why: pl.why });
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
await setCam(pl.cam);
|
|
719
|
+
await hideOverlays(page);
|
|
720
|
+
const file = node_path_1.default.join('poses', `${pl.id}.jpg`);
|
|
721
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), pl.id === 'pose.front' ? 900 : 450);
|
|
722
|
+
poseFacts.push({ id: pl.id, label: pl.label, file, camera: pl.cam, backdropHidden: hid });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
finally {
|
|
726
|
+
await frame.evaluate(() => globalThis.__vqaVeh.lightsOff(false)).catch(() => undefined);
|
|
727
|
+
await frame.evaluate(() => globalThis.__vqaVeh.backdrop(false));
|
|
728
|
+
await clearCam();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch (e) {
|
|
732
|
+
errors.push(`poses: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// ---------------------------------------------------------------------------------------------- wheel face
|
|
736
|
+
// Preview with --reference-wheel only. The front-left wheel photographed square-on along its axle, the tyre centred and
|
|
737
|
+
// filling WHEEL_FACE_FILL of the half-height, on the plain backdrop with every lamp off: the same view of every car, so
|
|
738
|
+
// vehicle/wheelDesign.ts can read spoke count, finish and centre cap from a known centre and radius.
|
|
739
|
+
if (preview && !addOnMode && preview.wheelFace && phase('wheelface')) {
|
|
740
|
+
const wf = { file: null };
|
|
741
|
+
facts.wheelFace = wf;
|
|
742
|
+
try {
|
|
743
|
+
const r = (rows ?? []).find((x) => (x.drawnCentre ?? x.hub)[0] > 0) ?? (rows ?? [])[0];
|
|
744
|
+
if (!r)
|
|
745
|
+
throw new Error('no wheel geometry to frame');
|
|
746
|
+
const c = r.drawnCentre ?? r.hub;
|
|
747
|
+
const rad = r.radius ?? r.physicsRadius;
|
|
748
|
+
const dist = (rad / wheelDesign_1.WHEEL_FACE_FILL) / Math.tan((wheelDesign_1.WHEEL_FACE_FOV / 2) * Math.PI / 180);
|
|
749
|
+
const out = Math.sign(c[0]) || 1;
|
|
750
|
+
await frame.evaluate(() => globalThis.__vqaVeh.backdrop(true));
|
|
751
|
+
await frame.evaluate(() => globalThis.__vqaVeh.lightsOff(true)).catch(() => undefined);
|
|
752
|
+
try {
|
|
753
|
+
await setCam({ pos: [c[0] + out * dist, c[1], c[2]], target: [c[0], c[1], c[2]], fov: wheelDesign_1.WHEEL_FACE_FOV, near: 0.05 });
|
|
754
|
+
await hideOverlays(page);
|
|
755
|
+
const file = node_path_1.default.join('wheelface', 'candidate.jpg');
|
|
756
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), 700);
|
|
757
|
+
wf.file = file;
|
|
758
|
+
wf.wheel = r.id;
|
|
759
|
+
wf.tyreRadiusM = rad;
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
await frame.evaluate(() => globalThis.__vqaVeh.lightsOff(false)).catch(() => undefined);
|
|
763
|
+
await frame.evaluate(() => globalThis.__vqaVeh.backdrop(false));
|
|
764
|
+
await clearCam();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (e) {
|
|
768
|
+
errors.push(`wheelface: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// ---------------------------------------------------------------------------------------------- daylight paint sample
|
|
772
|
+
// Preview with --reference-paint only. The body paint ALONE (every other mesh hidden), lit
|
|
773
|
+
// exactly as the Try Now frame lights it: the world's sun, environment, exposure, tone mapping AND its local lights
|
|
774
|
+
// (the appearance poses switch the local lights off, which hid the bonnet hotspot an owner saw in Try Now). Six views,
|
|
775
|
+
// so a glare that only catches the top surfaces of a front three-quarter still counts. Judged by vehicle/paintHue.ts.
|
|
776
|
+
if (preview && !addOnMode && preview.paint && phase('paint')) {
|
|
777
|
+
const paintFacts = { views: [], materials: null };
|
|
778
|
+
facts.paint = paintFacts;
|
|
779
|
+
try {
|
|
780
|
+
const plans = (0, vehiclePoses_1.planPoses)({ min: carMin, max: bb.max }, null).filter((pl) => pl.id !== 'pose.cockpit');
|
|
781
|
+
paintFacts.lightingBefore = await frame.evaluate(() => globalThis.__vqaVeh.lighting()).catch((e) => String(e));
|
|
782
|
+
await frame.evaluate(() => globalThis.__vqaVeh.backdrop(true));
|
|
783
|
+
try {
|
|
784
|
+
paintFacts.materials = await frame.evaluate(() => globalThis.__vqaVeh.paintOnly(true));
|
|
785
|
+
for (const pl of plans) {
|
|
786
|
+
if (!pl.cam) {
|
|
787
|
+
paintFacts.views.push({ id: pl.id, file: null, why: pl.why });
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
await setCam(pl.cam);
|
|
791
|
+
await hideOverlays(page);
|
|
792
|
+
const file = node_path_1.default.join('paint', `${pl.id.replace('pose.', 'paint.')}.jpg`);
|
|
793
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), 450);
|
|
794
|
+
// the same camera with the paint hidden too: what the world draws behind the car, so the paint's pixels are exactly
|
|
795
|
+
// the ones that differ (the sky and fog are drawn per camera angle, so no flat key colour holds)
|
|
796
|
+
await frame.evaluate(() => globalThis.__vqaVeh.paintShow(false));
|
|
797
|
+
const emptyFile = node_path_1.default.join('paint', `${pl.id.replace('pose.', 'empty.')}.jpg`);
|
|
798
|
+
try {
|
|
799
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, emptyFile), 450);
|
|
800
|
+
}
|
|
801
|
+
finally {
|
|
802
|
+
await frame.evaluate(() => globalThis.__vqaVeh.paintShow(true));
|
|
803
|
+
}
|
|
804
|
+
paintFacts.views.push({ id: pl.id, file, emptyFile });
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
finally {
|
|
808
|
+
await frame.evaluate(() => globalThis.__vqaVeh.paintOnly(false)).catch(() => undefined);
|
|
809
|
+
await frame.evaluate(() => globalThis.__vqaVeh.backdrop(false));
|
|
810
|
+
await clearCam();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (e) {
|
|
814
|
+
errors.push(`paint: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// ---------------------------------------------------------------------------------------------- articulation
|
|
818
|
+
if (boarded && phase('articulation')) {
|
|
819
|
+
for (const p of panels) {
|
|
820
|
+
try {
|
|
821
|
+
panelSweeps.push(await sweepPanel(page, frame, p, bb, shot, setCam));
|
|
822
|
+
}
|
|
823
|
+
catch (e) {
|
|
824
|
+
errors.push(`articulation ${p.id}: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
facts.articulation = panelSweeps;
|
|
829
|
+
mark('articulation');
|
|
830
|
+
// ---------------------------------------------------------------------------------------------- lamps
|
|
831
|
+
// Press each lamp key as the seated driver and read the emissive of every mesh the package's lamp rows resolve to
|
|
832
|
+
// (vehicleLamps.ts). Runs with the articulation phase; a package with no lamp rows has nothing to sweep.
|
|
833
|
+
if (boarded && !addOnMode && (phase('lamps') || phase('articulation'))) {
|
|
834
|
+
try {
|
|
835
|
+
const lampPlan = (0, vehicleLamps_1.planLamps)(ctx.resolved.root);
|
|
836
|
+
if (lampPlan.functions.length > 0) {
|
|
837
|
+
const sweep = await (0, vehicleLamps_1.runLampSweep)(page, frame, lampPlan);
|
|
838
|
+
lampJudgement = sweep.samples.length > 0 ? (0, vehicleLamps_1.judgeLamps)({ plan: lampPlan, samples: sweep.samples, bounds: bodyBounds }) : null;
|
|
839
|
+
facts.lamps = {
|
|
840
|
+
plan: lampPlan, atRest: sweep.atRest, unoccupied: unoccupiedLamps ? { lights: unoccupiedLamps.lights, bits: unoccupiedLamps.bits, lenses: unoccupiedLamps.meshes.filter((m) => m.capable).map((m) => [m.name, m.lum]) } : null, errors: sweep.errors, judgement: lampJudgement,
|
|
841
|
+
// the reads themselves, so a failing verdict can be audited line by line
|
|
842
|
+
samples: sweep.samples.map((x) => ({ state: x.state, lights: x.snaps[x.snaps.length - 1]?.lights ?? [], reads: x.snaps.map((n) => n.meshes.filter((m) => m.capable || lampPlan.slots.some((sl) => m.aliases.includes(sl))).map((m) => [m.name, m.lum, m.box ? [m.box.min[2], m.box.max[2]] : null])) })),
|
|
843
|
+
};
|
|
844
|
+
if (sweep.errors.length)
|
|
845
|
+
errors.push(...sweep.errors.map((x) => `lamps: ${x}`));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
catch (e) {
|
|
849
|
+
errors.push(`lamps: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
mark('lamps');
|
|
853
|
+
// ---------------------------------------------------------------------------------------------- inspection (preview)
|
|
854
|
+
// The gauntlet inspection set's exterior half: 8 angles by day and at night, and each lamp function lit at night
|
|
855
|
+
// (vehicleInspection.ts). Preview only, and never in the critic bundle: vehicle/inspectionSheets.ts lays it out.
|
|
856
|
+
if (preview && !addOnMode && boarded && phase('inspection')) {
|
|
857
|
+
try {
|
|
858
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(ctx.outDir, 'inspection', 'frames'), { recursive: true });
|
|
859
|
+
const insp = await (0, vehicleInspection_1.captureInspection)({
|
|
860
|
+
page, frame, carMin, carMax: bb.max, setCam, clearCam,
|
|
861
|
+
lampSignals: (0, vehicleLamps_1.planLamps)(ctx.resolved.root).functions.map((f) => f.signal),
|
|
862
|
+
outDir: ctx.outDir,
|
|
863
|
+
snap: async (id, settleMs) => {
|
|
864
|
+
const file = node_path_1.default.join('inspection', 'frames', `${id}.jpg`);
|
|
865
|
+
await hideOverlays(page);
|
|
866
|
+
await (0, browser_1.captureJpeg)(page, node_path_1.default.join(ctx.outDir, file), settleMs);
|
|
867
|
+
return file;
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
facts.inspection = insp;
|
|
871
|
+
if (insp.errors.length)
|
|
872
|
+
errors.push(...insp.errors.map((x) => `inspection: ${x}`));
|
|
873
|
+
}
|
|
874
|
+
catch (e) {
|
|
875
|
+
errors.push(`inspection: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
mark('inspection');
|
|
879
|
+
// ---------------------------------------------------------------------------------------------- steering
|
|
880
|
+
if (boarded && rows && phase('steering')) {
|
|
881
|
+
try {
|
|
882
|
+
steering = await steeringSweep(page, frame, rows, shot, setCam, clearCam);
|
|
883
|
+
facts.steering = steering.measured;
|
|
884
|
+
}
|
|
885
|
+
catch (e) {
|
|
886
|
+
errors.push(`steering: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
mark('steering');
|
|
890
|
+
// ---------------------------------------------------------------------------------------------- driver's view
|
|
891
|
+
if (boarded && phase('cabin')) {
|
|
892
|
+
try {
|
|
893
|
+
await clearCam();
|
|
894
|
+
const fp = await setCameraMode(page, frame, 'first-person');
|
|
895
|
+
facts.cabinCamera = fp;
|
|
896
|
+
await shot('cabin.driver', "Driver's view (first person)", 'cabin', 900);
|
|
897
|
+
await setCameraMode(page, frame, 'third-person');
|
|
898
|
+
}
|
|
899
|
+
catch (e) {
|
|
900
|
+
errors.push(`cabin view: ${String(e?.message ?? e).slice(0, 200)}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
mark('cabin');
|
|
904
|
+
// ---------------------------------------------------------------------------------------------- drive + spin
|
|
905
|
+
if (boarded && rows && phase('drive')) {
|
|
906
|
+
try {
|
|
907
|
+
const d = await driveAndSample(page, frame, rows, shot, clearCam, bodyBounds);
|
|
908
|
+
spin = d.spin;
|
|
909
|
+
spinMode = d.mode;
|
|
910
|
+
facts.drive = d.facts;
|
|
911
|
+
const alive = (0, vehicleDriveAlive_1.judgeDriveAlive)(d.alive);
|
|
912
|
+
facts.driveAlive = alive.measured;
|
|
913
|
+
asserts.push({ id: 'vehicle.drive.responds_with_audio', pass: alive.pass, severity: 'block', subject: itemId, detail: alive.detail, measured: alive.measured });
|
|
914
|
+
if (alive.harnessError)
|
|
915
|
+
errors.push(alive.harnessError);
|
|
916
|
+
if (process.env.HELIX_VQA_DEBUG_PARK_AT_WALL)
|
|
917
|
+
errors.push('HELIX_VQA_DEBUG_PARK_AT_WALL was set: the drive parked the car against the scenery ahead (a debug run, not for submission)');
|
|
918
|
+
}
|
|
919
|
+
catch (e) {
|
|
920
|
+
const why = String(e?.message ?? e).slice(0, 200);
|
|
921
|
+
errors.push(`drive: ${why}`);
|
|
922
|
+
if (!asserts.some((a) => a.id === 'vehicle.drive.responds_with_audio'))
|
|
923
|
+
asserts.push({ id: 'vehicle.drive.responds_with_audio', pass: false, severity: 'block', subject: itemId, detail: `the drive could not complete: ${why}` });
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
mark('drive');
|
|
927
|
+
seatEvidence = await ev(`(() => { const v = globalThis.__assetGym.mp.portableVehicleEvidence().vehicles[0]; const l = globalThis.__assetGym.mp.seatEvidence?.().local; return { seats: v?.seats ?? [], occupied: v?.occupiedSeats ?? null, seated: l?.seated ? { seatId: l.seated.seatId, role: l.seated.role } : null }; })()`).catch(() => null);
|
|
928
|
+
facts.seats = { declared: seatsDeclared, evidence: seatEvidence, boardedAs: facts.board?.seated ?? null };
|
|
929
|
+
mark('seats');
|
|
930
|
+
// ---------------------------------------------------------------------------------------------- occupants
|
|
931
|
+
// Last, because it leaves the driver's seat: every declared seat gets a real occupant (this account's own avatar,
|
|
932
|
+
// walked to the door and seated with the real E key), measured against the DRAWN cabin and photographed from outside.
|
|
933
|
+
if (boarded && phase('occupants')) {
|
|
934
|
+
try {
|
|
935
|
+
const occ = await (0, vehicleOccupants_1.captureOccupants)({
|
|
936
|
+
page, frame, itemId, panels, seatsDeclared, seatEvidence: seatEvidence?.seats ?? [], carBounds: bodyBounds, outDir: dumpDir, shot, setCam, clearCam,
|
|
937
|
+
evidence: (id, label, group, overlay) => evidenceShot(id, label, group, overlay), evidencePrefix,
|
|
938
|
+
});
|
|
939
|
+
// the worst EVIDENCE_MAX clashes of the whole car (they were already deduped per seat, body part and cabin part)
|
|
940
|
+
const kept = [...occ.evidence].sort((x, y) => y.score - x.score).slice(0, exports.EVIDENCE_MAX);
|
|
941
|
+
for (const e of occ.evidence) {
|
|
942
|
+
if (kept.includes(e))
|
|
943
|
+
frames.push(e.shot);
|
|
944
|
+
else
|
|
945
|
+
node_fs_1.default.rmSync(node_path_1.default.join(ctx.outDir, e.shot.file), { force: true });
|
|
946
|
+
}
|
|
947
|
+
facts.penetrationEvidence = kept.map((e) => e.id);
|
|
948
|
+
occupantMeasures.push(...occ.measures);
|
|
949
|
+
occupantFailed.push(...occ.failed);
|
|
950
|
+
gripWindows.push(...occ.grip);
|
|
951
|
+
gripSeats.push(...occ.seated);
|
|
952
|
+
rigWarnings.push(...occ.rigWarnings);
|
|
953
|
+
facts.occupants = occ.facts;
|
|
954
|
+
}
|
|
955
|
+
catch (e) {
|
|
956
|
+
errors.push(`occupants: ${String(e?.message ?? e).slice(0, 300)}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
mark('occupants');
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch (e) {
|
|
963
|
+
errors.push(String(e?.message ?? e).slice(0, 400));
|
|
964
|
+
await page.screenshot({ path: node_path_1.default.join(ctx.outDir, 'frames', 'error.jpg'), type: 'jpeg', quality: 70 }).catch(() => undefined);
|
|
965
|
+
}
|
|
966
|
+
finally {
|
|
967
|
+
facts.shim = shim;
|
|
968
|
+
facts.pageErrors = b.pageErrors.slice(0, 20);
|
|
969
|
+
engineVersion = engineVersion ?? engineVersionFrom(scriptUrls);
|
|
970
|
+
facts.runtimeScripts = [...new Set(scriptUrls.filter((u) => /index\.js/.test(u)))].slice(0, 20);
|
|
971
|
+
// closing with live route handlers / a running world can stall Playwright for ~30 s: drop the routes and the
|
|
972
|
+
// world first (bounded), then close
|
|
973
|
+
await b.ctx.unrouteAll({ behavior: 'ignoreErrors' }).catch(() => undefined);
|
|
974
|
+
await page.goto('about:blank', { timeout: 5000 }).catch(() => undefined);
|
|
975
|
+
await Promise.race([b.close().catch(() => undefined), sleep(15_000)]);
|
|
976
|
+
mark('closed');
|
|
977
|
+
}
|
|
978
|
+
// ---------------------------------------------------------------------------------------------------- asserts
|
|
979
|
+
const notMeasured = (id, why) => ({ id, pass: false, severity: 'block', subject: itemId, detail: `not measured: ${why}` });
|
|
980
|
+
if (addOnMode) {
|
|
981
|
+
if (!preview?.baselineOnly && addOnProbe) {
|
|
982
|
+
const stock = preview?.stockTuning ?? null;
|
|
983
|
+
for (const [index, addOn] of fittedAddOns.entries()) {
|
|
984
|
+
const tuning = addOn.kind === 'tune' && stock && addOnTuning ? (0, previewAddon_1.judgeTuneOps)(addOn, stock, addOnTuning) : null;
|
|
985
|
+
for (const v of (0, previewAddon_1.judgeAddOn)(index, addOn, addOnProbe, { toleranceCm: preview?.toleranceCm, tuning })) {
|
|
986
|
+
asserts.push({ id: v.id, pass: v.pass, severity: v.severity, subject: itemId, detail: v.detail, ...(v.measured ? { measured: v.measured } : {}) });
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
else if (!preview?.baselineOnly)
|
|
991
|
+
errors.push('the fitted add-ons were never measured');
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
if (rows) {
|
|
995
|
+
const h = (0, vehicleMath_1.judgeAtHubs)(rows, physicsWheelCount, duplicates);
|
|
996
|
+
asserts.push({ id: 'vehicle.wheels.at_hubs', pass: h.pass, severity: 'block', subject: itemId, detail: h.detail, measured: h.measured });
|
|
997
|
+
const a = (0, vehicleMath_1.judgeArchClearance)(rows, ride ?? { minM: null, mesh: null, at: null });
|
|
998
|
+
asserts.push({ id: 'vehicle.wheels.arch_clearance', pass: a.pass, severity: 'block', subject: itemId, detail: a.detail, measured: a.measured });
|
|
999
|
+
const rr = (0, vehicleMath_1.judgeRadius)(rows);
|
|
1000
|
+
asserts.push({ id: 'vehicle.wheels.radius_matches_physics', pass: rr.pass, severity: 'warn', subject: itemId, detail: rr.detail, measured: rr.measured });
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
asserts.push(notMeasured('vehicle.wheels.at_hubs', 'the wheel geometry was never read'), notMeasured('vehicle.wheels.arch_clearance', 'the wheel geometry was never read'));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (preview) {
|
|
1007
|
+
const declared = (0, vehicleStance_1.declaredStance)(ctx.resolved.root);
|
|
1008
|
+
if (addOnMode) { /* the stance and wheel judgements belong to the car preview; an add-on preview judges the fitted parts */ }
|
|
1009
|
+
else {
|
|
1010
|
+
if (stanceMeasured) {
|
|
1011
|
+
const st = (0, vehicleStance_1.judgeStance)(declared, stanceMeasured);
|
|
1012
|
+
asserts.push({ id: 'preview.stance.ride_height', pass: st.pass, severity: 'block', subject: itemId, detail: st.detail, measured: st.measured });
|
|
1013
|
+
}
|
|
1014
|
+
else
|
|
1015
|
+
asserts.push(notMeasured('preview.stance.ride_height', 'the wheel geometry was never read'));
|
|
1016
|
+
if (rows) {
|
|
1017
|
+
const wb = (0, vehicleStance_1.judgeWheelBinding)(rows, (facts.strayWheelMeshes ?? []));
|
|
1018
|
+
asserts.push({ id: 'preview.wheels.bound_to_hubs', pass: wb.pass, severity: 'block', subject: itemId, detail: wb.detail, measured: wb.measured });
|
|
1019
|
+
}
|
|
1020
|
+
const sum = (0, vehicleStance_1.judgeDeclaredSum)(declared);
|
|
1021
|
+
asserts.push({ id: 'preview.stance.declared_consistent', pass: sum.pass, severity: 'warn', subject: itemId, detail: sum.detail, measured: sum.measured });
|
|
1022
|
+
if (spin && rows) {
|
|
1023
|
+
const roll = (0, vehicleStance_1.rollingRadii)(spin, rows.map((r) => r.rollRadius ?? r.physicsRadius));
|
|
1024
|
+
facts.rollingRadius = roll;
|
|
1025
|
+
const rv = (0, vehicleStance_1.judgeRollingRadius)(roll, declared.wheelRadiusM);
|
|
1026
|
+
asserts.push({ id: 'preview.wheels.rolling_radius', pass: rv.pass, severity: 'warn', subject: itemId, detail: rv.detail, measured: rv.measured });
|
|
1027
|
+
}
|
|
1028
|
+
else
|
|
1029
|
+
asserts.push(notMeasured('preview.wheels.rolling_radius', boarded ? 'the drive did not complete' : 'could not board to drive'));
|
|
1030
|
+
}
|
|
1031
|
+
facts.preview = {
|
|
1032
|
+
proxyItem: itemId,
|
|
1033
|
+
authorityGateLifted: shim.authorityGate,
|
|
1034
|
+
sessionsRewritten: previewSink?.sessions.length ?? 0,
|
|
1035
|
+
sessionMisses: previewSink?.sessionMisses ?? [],
|
|
1036
|
+
candidateGlbServedFromDisk: previewSink?.glbServed ?? 0,
|
|
1037
|
+
candidateAudioServedFromDisk: previewSink?.audioServed ?? 0,
|
|
1038
|
+
proxyModelFetched: previewSink?.proxyModelFetched ?? [],
|
|
1039
|
+
thresholds: vehicleStance_1.STANCE_THRESHOLDS,
|
|
1040
|
+
};
|
|
1041
|
+
if (shim.authorityGate === 0 && previewSink?.sessions.some((x) => x.candidateChecksum !== x.proxyChecksum))
|
|
1042
|
+
errors.push('the runtime bundle has no `authorityBuild !== requestedBuild` gate to lift (the engine changed), so a candidate that differs from its proxy cannot be drawn; the preview shim needs updating');
|
|
1043
|
+
if (!previewSink?.sessions.length)
|
|
1044
|
+
errors.push(`the Try Now session of proxy item ${itemId} was never rewritten, so the candidate was not served${previewSink?.sessionMisses.length ? ` (${previewSink.sessionMisses.join('; ')})` : ''}`);
|
|
1045
|
+
else if (!previewSink.glbServed && preview.candidate.keepCar !== true)
|
|
1046
|
+
errors.push('the candidate GLB was never fetched from disk, so the runtime drew some other model');
|
|
1047
|
+
for (const a of fittedAddOns)
|
|
1048
|
+
if (a.kind !== 'tune' && a.glb && !previewSink?.addOnGlbServed[a.itemId] && !preview.baselineOnly)
|
|
1049
|
+
errors.push(`the add-on model for ${a.title} was never fetched from disk, so the runtime did not draw it`);
|
|
1050
|
+
facts.preview = { ...facts.preview, addOns: previewSink?.sessions[previewSink.sessions.length - 1]?.addOns ?? [], addOnGlbServedFromDisk: previewSink?.addOnGlbServed ?? {}, compositionDigest: previewSink?.sessions[previewSink.sessions.length - 1]?.compositionDigest ?? null };
|
|
1051
|
+
if (previewSink?.proxyModelFetched.length)
|
|
1052
|
+
errors.push(`the proxy's own model was fetched (${previewSink.proxyModelFetched.map((u) => u.slice(-24)).join(', ')}), so the runtime may have drawn the proxy car, not the candidate`);
|
|
1053
|
+
}
|
|
1054
|
+
if (!addOnMode) {
|
|
1055
|
+
if (spin) {
|
|
1056
|
+
asserts.push({
|
|
1057
|
+
id: 'vehicle.wheels.spin_with_speed', pass: spin.pass, severity: 'block', subject: itemId,
|
|
1058
|
+
detail: spin.pass
|
|
1059
|
+
? `rolling ${spin.distanceM.toFixed(1)} m in ${spin.seconds.toFixed(2)} s (${spinMode}), every drawn wheel turned with distance/radius (ratios ${spin.wheels.map((w) => w.ratio?.toFixed(2)).join(', ')})`
|
|
1060
|
+
: `drawn wheel spin does not follow speed: ${spin.reason} (${spinMode})`,
|
|
1061
|
+
measured: { mode: spinMode, ...spin },
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
else if (!measureOnly)
|
|
1065
|
+
asserts.push(notMeasured('vehicle.wheels.spin_with_speed', boarded ? 'the drive did not complete' : 'could not board to drive'));
|
|
1066
|
+
if (steering)
|
|
1067
|
+
asserts.push({ id: 'vehicle.steering.wheel_rotates', pass: steering.pass, severity: 'block', subject: itemId, detail: steering.detail, measured: steering.measured });
|
|
1068
|
+
else if (!measureOnly)
|
|
1069
|
+
asserts.push(notMeasured('vehicle.steering.wheel_rotates', boarded ? 'the steering sweep did not complete' : 'could not board to steer'));
|
|
1070
|
+
if (boarded) {
|
|
1071
|
+
const verdicts = panelSweeps.map((p) => ({ p, v: (0, vehicleMath_1.judgePanel)(p) }));
|
|
1072
|
+
const missing = panels.filter((p) => !panelSweeps.some((s) => s.id === p.id)).map((p) => p.id);
|
|
1073
|
+
const failed = verdicts.filter((x) => !x.v.pass);
|
|
1074
|
+
asserts.push({
|
|
1075
|
+
id: 'vehicle.articulation.reaches_limits', pass: failed.length === 0 && missing.length === 0, severity: 'block', subject: itemId,
|
|
1076
|
+
detail: !panels.length ? 'the package declares no articulated panels'
|
|
1077
|
+
: missing.length ? `${missing.join(', ')} could not be operated`
|
|
1078
|
+
: failed.length ? failed.map((x) => x.v.why).join('; ')
|
|
1079
|
+
: `${panels.length} panels reach their declared limits and close: ${verdicts.map((x) => x.v.why).join(', ')}`,
|
|
1080
|
+
measured: { panels: panelSweeps, missing },
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
else
|
|
1084
|
+
asserts.push(notMeasured('vehicle.articulation.reaches_limits', 'could not board to operate the panels'));
|
|
1085
|
+
if (lampJudgement)
|
|
1086
|
+
asserts.push({ id: 'vehicle.lamps.each_function_lights', pass: lampJudgement.pass, severity: 'block', subject: itemId, detail: lampJudgement.detail, measured: { functions: lampJudgement.functions, darkLenses: lampJudgement.darkLenses, skipped: lampJudgement.skipped } });
|
|
1087
|
+
{
|
|
1088
|
+
const { absent, resolved, driverId } = (0, vehicleMath_1.resolveDeclaredSeats)(seatsDeclared, seatEvidence?.seats ?? []);
|
|
1089
|
+
const seatedId = facts.board?.seated?.seatId ?? null;
|
|
1090
|
+
const driverOk = boarded && (!seatsDeclared.driver || seatedId === driverId);
|
|
1091
|
+
const shown = seatsDeclared.seats.map((s) => (resolved[s] && resolved[s] !== s ? `${s} -> ${resolved[s]}` : s));
|
|
1092
|
+
asserts.push({
|
|
1093
|
+
id: 'vehicle.seats.addressable', pass: !!seatEvidence && absent.length === 0 && driverOk && seatsDeclared.seats.length > 0, severity: 'block', subject: itemId,
|
|
1094
|
+
detail: !seatEvidence ? 'no seat evidence was read'
|
|
1095
|
+
: !seatsDeclared.seats.length ? 'the package declares no seats'
|
|
1096
|
+
: absent.length ? `declared seats missing from seat evidence: ${absent.join(', ')}`
|
|
1097
|
+
: !driverOk ? `driver seat ${seatsDeclared.driver} could not be boarded (seated: ${seatedId ?? 'none'})`
|
|
1098
|
+
: `${seatsDeclared.seats.length} declared seats present (${shown.join(', ')}); boarded ${seatedId} as driver`,
|
|
1099
|
+
measured: { declared: seatsDeclared, evidence: seatEvidence?.seats ?? null, seated: seatedId, resolved },
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const seatBridge = facts.seatBridge;
|
|
1103
|
+
if (preview && !addOnMode && seatBridge?.report) {
|
|
1104
|
+
const rep = seatBridge.report;
|
|
1105
|
+
const seatedId = facts.board?.seated?.seatId ?? null;
|
|
1106
|
+
const candidateSeats = new Set([...rep.candidate.declaredCabinSeats, ...rep.candidate.glbSeatNodes]);
|
|
1107
|
+
const key = seatedId ? rep.table.candidateKeyForCabin[seatedId] ?? null : null;
|
|
1108
|
+
const sends = seatBridge.log ?? [];
|
|
1109
|
+
const ok = boarded && !!seatedId && candidateSeats.has(seatedId);
|
|
1110
|
+
asserts.push({
|
|
1111
|
+
id: 'preview.seats.candidate_ids_used', pass: ok, severity: 'block', subject: itemId,
|
|
1112
|
+
detail: ok ? `boarded the candidate's own seat ${seatedId}${key ? ` (its manifest key "${key}")` : ''}; the room, which holds the stand-in's seats (${rep.proxy.keys.join(', ') || 'none'}), was asked in its own vocabulary: ${sends.length ? `${sends.filter((x) => x.translated).length} of ${sends.length} seat send(s) translated` : rep.table.identical ? 'the two vocabularies already agree, nothing rewritten' : 'no seat send was observed'}`
|
|
1113
|
+
: !boarded ? `could not board with the candidate's seat ids (candidate seats ${[...candidateSeats].join(', ') || 'none'}, key ${rep.candidate.driverKey ?? 'none'}; stand-in seats ${rep.proxy.keys.join(', ') || 'none'}); ${rep.table.problems.join('; ') || (seatBridge.installed?.ok === false ? `the seat bridge was not installed (${seatBridge.installed.why})` : 'the room refused the seat request')}`
|
|
1114
|
+
: `seated in ${seatedId}, which is not one of the candidate's seats (${[...candidateSeats].join(', ')}): the stand-in's seat was used`,
|
|
1115
|
+
measured: { candidateSeatIds: [...candidateSeats], candidateKeys: rep.candidate.keys, standInKeys: rep.proxy.keys, seated: seatedId, seatedKey: key, identical: rep.table.identical, unmapped: rep.table.unmapped, sends },
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
if (phase('occupants')) {
|
|
1119
|
+
const { resolved } = (0, vehicleMath_1.resolveDeclaredSeats)(seatsDeclared, seatEvidence?.seats ?? []);
|
|
1120
|
+
const declared = seatsDeclared.seats;
|
|
1121
|
+
if (boarded) {
|
|
1122
|
+
for (const v of [...(0, vehicleMath_1.judgeOccupants)(occupantMeasures, occupantFailed, declared, engineVersion), ...(0, vehiclePosture_1.judgePosture)(occupantMeasures), ...(0, vehicleGrip_1.judgeGrip)(gripWindows, gripSeats), (0, vehicleGrip_1.judgeSeatRigWarnings)(rigWarnings, gripSeats.length)]) {
|
|
1123
|
+
asserts.push({ id: v.id, pass: v.pass, severity: v.severity ?? 'block', subject: itemId, detail: v.detail, measured: v.measured });
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
for (const id of ['vehicle.occupants.engine_current', 'vehicle.occupants.boarded', 'vehicle.occupants.head_clearance', 'vehicle.occupants.hips_on_seat', 'vehicle.occupants.feet_on_floor', 'vehicle.occupants.hands_on_wheel', 'vehicle.occupants.penetration', 'vehicle.occupants.torso_angle', 'vehicle.occupants.knees_up', 'vehicle.occupants.head_in_wings', ...vehicleGrip_1.GRIP_ASSERT_IDS]) {
|
|
1128
|
+
asserts.push(notMeasured(id, 'could not board to seat an occupant'));
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
facts.occupantEngine = { engineVersion, min: vehicleMath_1.OCCUPANT_MIN_ENGINE, declared, resolved };
|
|
1132
|
+
// Preview: did the runtime seat each occupant where the package put the seat? (its cabin fit moves seats silently)
|
|
1133
|
+
if (preview && !addOnMode) {
|
|
1134
|
+
const seatsSeen = (facts.occupants?.seats ?? []).filter((x) => x.seatId);
|
|
1135
|
+
if (boarded && seatsSeen.length) {
|
|
1136
|
+
const cf = (0, vehicleCabinFit_1.judgeCabinFit)(seatsSeen);
|
|
1137
|
+
facts.cabinFit = { rows: cf.rows, report: cf.report, thresholds: vehicleCabinFit_1.CABIN_FIT_THRESHOLDS };
|
|
1138
|
+
asserts.push({ id: 'preview.occupants.cabin_fit', pass: cf.pass, severity: 'block', subject: itemId, detail: cf.detail, measured: { rows: cf.rows, maxMoveM: vehicleCabinFit_1.CABIN_FIT_THRESHOLDS.maxMoveM, steering: cf.report?.steering ?? null, pedals: cf.report?.pedals ?? null, driverFit: cf.report?.driverFit ?? null, warnings: cf.report?.warnings ?? [] } });
|
|
1139
|
+
}
|
|
1140
|
+
else
|
|
1141
|
+
asserts.push(notMeasured('preview.occupants.cabin_fit', boarded ? 'no seat was occupied' : 'could not board to seat an occupant'));
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (stats)
|
|
1146
|
+
asserts.push(...(0, commonAsserts_1.commonAsserts)('vehicle', stats, images, { subject: itemId }));
|
|
1147
|
+
else if (!preview?.baselineOnly)
|
|
1148
|
+
errors.push('scene stats were never read');
|
|
1149
|
+
// ---------------------------------------------------------------------------------------------------- byte identity
|
|
1150
|
+
const keepCar = preview?.candidate.keepCar === true;
|
|
1151
|
+
const modelHex = keepCar ? null : (0, resolve_1.hexOf)(pass.modelCid);
|
|
1152
|
+
facts.modelCid = pass.modelCid;
|
|
1153
|
+
if (!modelHex && !keepCar)
|
|
1154
|
+
errors.push('the resolved version names no model CID to check the rendered bytes against');
|
|
1155
|
+
const fetched = [...new Set([...b.fetchedAssets, ...contentUrls])];
|
|
1156
|
+
facts.modelFetchedFrom = fetched.filter((u) => modelHex && u.includes(modelHex));
|
|
1157
|
+
facts.contentFetched = contentUrls.map((u) => /[0-9a-f]{64}/.exec(u)?.[0] ?? u).slice(0, 40);
|
|
1158
|
+
if (subFacts) {
|
|
1159
|
+
if (!Number(subFacts.served))
|
|
1160
|
+
errors.push(`rendition ${pass.rendition}: the runtime never requested the model it was to be served in place of (${String(subFacts.from).slice(0, 24)}…)`);
|
|
1161
|
+
}
|
|
1162
|
+
else if (!modelHex) { /* reported above, or a published car's own bytes (--item) */ }
|
|
1163
|
+
else if (!fetched.some((u) => u.includes(modelHex)))
|
|
1164
|
+
errors.push(`rendered bytes are not the pinned model: no fetched asset URL contains ${modelHex.slice(0, 16)}…`);
|
|
1165
|
+
timings.total = +((Date.now() - started) / 1000).toFixed(1);
|
|
1166
|
+
facts.timings = timings;
|
|
1167
|
+
ctx.log('vehicle.timings', timings);
|
|
1168
|
+
return {
|
|
1169
|
+
route: 'test-grounds', url, frames, asserts, facts,
|
|
1170
|
+
subjects: [{ id: itemId, title: ctx.resolved.identity.title, role: 'host' }],
|
|
1171
|
+
errors: [...new Set(errors)], renderer, engineVersion, fetchedAssets: fetched,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
/** Phases a measure-only (non-default rendition) pass skips: steering and the drive are rendition-independent physics,
|
|
1175
|
+
* the cabin view is a frame. */
|
|
1176
|
+
const MEASURE_ONLY_SKIPS = new Set(['steering', 'cabin', 'drive']);
|
|
1177
|
+
/** In-bundle clipping evidence frames per pass (rendition): the worst clashes only, each one a different place. */
|
|
1178
|
+
exports.EVIDENCE_MAX = 3;
|
|
1179
|
+
/** A wheel whose drawn geometry fails at_hubs or arch_clearance on its own (the same per-wheel criteria they judge). */
|
|
1180
|
+
function wheelFailsGeometry(r, t = vehicleMath_1.VEHICLE_QA_THRESHOLDS) {
|
|
1181
|
+
const d = r.delta;
|
|
1182
|
+
const offHub = !!d && (Math.hypot(d[0], d[2]) > t.hubPlanarTolM || Math.abs(d[1]) > t.hubVerticalTolM);
|
|
1183
|
+
const tucked = r.archGapM !== null && r.archGapM !== undefined && r.archGapM < t.archGapMinM;
|
|
1184
|
+
const poked = (r.penetrationCount ?? 0) > t.penetrationMaxCount && (r.maxPenetrationM ?? 0) > t.penetrationDepthM;
|
|
1185
|
+
return offHub || tucked || poked;
|
|
1186
|
+
}
|
|
1187
|
+
const renderVehicle = async (ctx) => {
|
|
1188
|
+
// A preview draws the one candidate GLB it was handed: there are no sealed renditions to walk.
|
|
1189
|
+
if (ctx.preview)
|
|
1190
|
+
return vehiclePass(ctx, { rendition: null, tier: null, measureOnly: false, modelCid: ctx.resolved.modelCid });
|
|
1191
|
+
const started = Date.now();
|
|
1192
|
+
const plan = renditionPlan(ctx.resolved.modelVariants ?? [], ctx.resolved.modelCid, ctx.resolved.root);
|
|
1193
|
+
// Fail fast, before minutes of rendering, when the package would draw bytes the Version does not seal: the engine draws
|
|
1194
|
+
// the tier's rendition artifactRef, not the manifest variant, so the run would judge another car under this name.
|
|
1195
|
+
const unsealed = unsealedRenditionErrors(ctx.resolved.modelVariants ?? [], ctx.resolved.root, ctx.resolved.candidate?.vehiclePackage ?? null, ctx.resolved.identity.packageVersion ?? null);
|
|
1196
|
+
if (unsealed.length) {
|
|
1197
|
+
return {
|
|
1198
|
+
route: 'test-grounds', url: '', frames: [], asserts: [], facts: { renditionsNotSealed: unsealed }, subjects: [{ id: ctx.resolved.identity.itemId, title: ctx.resolved.identity.title, role: 'host' }],
|
|
1199
|
+
errors: unsealed, renderer: null, engineVersion: null, fetchedAssets: [],
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
const only = (process.env.HELIX_VQA_RENDITION_ONLY ?? '').trim();
|
|
1203
|
+
const debugErrors = only ? [`HELIX_VQA_RENDITION_ONLY=${only} was set: a partial debug run, not for submission`] : [];
|
|
1204
|
+
const tag = tagRendition;
|
|
1205
|
+
const passes = [];
|
|
1206
|
+
const runPass = async (spec) => {
|
|
1207
|
+
const t0 = Date.now();
|
|
1208
|
+
ctx.log('vehicle.rendition', { rendition: spec.rendition, tier: spec.tier ?? 'auto', measureOnly: spec.measureOnly });
|
|
1209
|
+
const r = await vehiclePass(ctx, spec);
|
|
1210
|
+
passes.push({
|
|
1211
|
+
rendition: spec.rendition, tierThatSelectsIt: spec.tier, measureOnly: spec.measureOnly, modelCid: spec.modelCid, substitution: r.facts.substitution ?? null,
|
|
1212
|
+
drawnTriangles: r.facts.drawnTriangles ?? null,
|
|
1213
|
+
seconds: +((Date.now() - t0) / 1000).toFixed(1), modelFetched: (r.facts.modelFetchedFrom ?? []).length > 0,
|
|
1214
|
+
contentFetched: r.facts.contentFetched ?? null, diagnosticFrame: r.facts.diagnosticFrame ?? null, errors: r.errors,
|
|
1215
|
+
asserts: r.asserts.map((a) => ({ id: a.id, pass: a.pass, severity: a.severity })),
|
|
1216
|
+
});
|
|
1217
|
+
return r;
|
|
1218
|
+
};
|
|
1219
|
+
const def = plan.default;
|
|
1220
|
+
const main = only && def && only !== def.id ? null : await runPass({ rendition: def?.id ?? null, tier: null, measureOnly: false, modelCid: ctx.resolved.modelCid });
|
|
1221
|
+
const asserts = main ? tag(main.asserts, def?.id ?? null) : [];
|
|
1222
|
+
const errors = [...debugErrors, ...(main?.errors ?? []), ...plan.errors];
|
|
1223
|
+
const fetched = new Set(main?.fetchedAssets ?? []);
|
|
1224
|
+
const extra = {};
|
|
1225
|
+
const extraFrames = []; // the other renditions' evidence frames (they take no others)
|
|
1226
|
+
const done = new Map();
|
|
1227
|
+
if (main && def)
|
|
1228
|
+
done.set((0, resolve_1.hexOf)(def.cid) ?? def.id, main);
|
|
1229
|
+
for (const r of plan.others) {
|
|
1230
|
+
if (only && only !== r.id)
|
|
1231
|
+
continue;
|
|
1232
|
+
const same = done.get((0, resolve_1.hexOf)(r.cid) ?? r.id);
|
|
1233
|
+
if (same) {
|
|
1234
|
+
// the same bytes as a rendition already measured: its measured asserts stand for this one too
|
|
1235
|
+
asserts.push(...tag(same.asserts.filter((a) => exports.PER_RENDITION_ASSERT.test(a.id)), r.id));
|
|
1236
|
+
passes.push({ rendition: r.id, sameBytesAs: same.facts.rendition ?? null, modelCid: r.cid });
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
const url = artifactUrl(ctx.resolved.root, r.id, r.cid, ctx.apiBase);
|
|
1240
|
+
const res = await runPass({ rendition: r.id, tier: r.tier, measureOnly: true, modelCid: r.cid, substitute: def ? { fromCid: def.cid, toCid: r.cid, url } : null });
|
|
1241
|
+
done.set((0, resolve_1.hexOf)(r.cid) ?? r.id, res);
|
|
1242
|
+
asserts.push(...tag(res.asserts, r.id));
|
|
1243
|
+
extraFrames.push(...res.frames);
|
|
1244
|
+
errors.push(...res.errors.map((e) => `rendition ${r.id}: ${e}`));
|
|
1245
|
+
for (const u of res.fetchedAssets)
|
|
1246
|
+
fetched.add(u);
|
|
1247
|
+
extra[r.id] = { wheels: res.facts.wheels, rideHeight: res.facts.rideHeight, occupants: res.facts.occupants, articulation: res.facts.articulation, timings: res.facts.timings, packageIdentity: res.facts.packageIdentity, runtimeEvidence: res.facts.runtimeEvidence };
|
|
1248
|
+
}
|
|
1249
|
+
const facts = { ...(main?.facts ?? {}), renditions: { default: def?.id ?? null, declared: plan.declared, passes, measured: extra }, renditionWallSeconds: +((Date.now() - started) / 1000).toFixed(1) };
|
|
1250
|
+
return {
|
|
1251
|
+
route: 'test-grounds', url: main?.url ?? '', frames: [...(main?.frames ?? []), ...extraFrames], asserts, facts,
|
|
1252
|
+
subjects: main?.subjects ?? [{ id: ctx.resolved.identity.itemId, title: ctx.resolved.identity.title, role: 'host' }],
|
|
1253
|
+
errors: [...new Set(errors)], renderer: main?.renderer ?? null, engineVersion: main?.engineVersion ?? null, fetchedAssets: [...fetched],
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
exports.renderVehicle = renderVehicle;
|
|
1257
|
+
/** Every assert of a pass carries the rendition it was measured on (none when the package declares no renditions). */
|
|
1258
|
+
function tagRendition(asserts, rendition) {
|
|
1259
|
+
return rendition ? asserts.map((a) => ({ ...a, rendition })) : asserts;
|
|
1260
|
+
}
|
|
1261
|
+
/** The asserts measured on the drawn model (what a rendition can change); the rest are physics or input. */
|
|
1262
|
+
exports.PER_RENDITION_ASSERT = /^(vehicle\.wheels\.(at_hubs|arch_clearance|radius_matches_physics)|vehicle\.articulation\.|vehicle\.seats\.|vehicle\.occupants\.|scene\.|render\.|scale\.|frame\.)/;
|
|
1263
|
+
/** Where a rendition's sealed bytes live: its `mesh.visual.renditions[].artifactRef` (checked against the CID), else the
|
|
1264
|
+
* Continuum public object of that CID. */
|
|
1265
|
+
function artifactUrl(root, id, cid, apiBase) {
|
|
1266
|
+
const hex = (0, resolve_1.hexOf)(cid) ?? '';
|
|
1267
|
+
const rows = (root?.mesh?.visual?.renditions ?? []);
|
|
1268
|
+
const row = rows.find((x) => x.id === id && typeof x.artifactRef === 'string' && (!x.checksumSha256 || x.checksumSha256 === hex))
|
|
1269
|
+
?? rows.find((x) => typeof x.artifactRef === 'string' && x.checksumSha256 === hex);
|
|
1270
|
+
return row?.artifactRef ?? `${apiBase.replace(/\/$/, '')}/api/v1/continuum/public/objects/sha256:${hex}`;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* The model renditions a vehicle package declares (manifest variants with a `vehicle.model.web.*` role). The default is
|
|
1274
|
+
* the one the gate's model CID names (target-balanced); every other one is rendered through the tier that selects it,
|
|
1275
|
+
* the tier with the richest interior when several do (a `minimal` tier hides the cabin).
|
|
1276
|
+
*/
|
|
1277
|
+
/** The sealed root's and the candidate vehicle package's renditions vs the Version's GLB variants (`vehicle/renditionManifest.ts`). */
|
|
1278
|
+
function unsealedRenditionErrors(variants, root, candidatePackage, version) {
|
|
1279
|
+
const v = version ? `Version ${version}` : 'this Version';
|
|
1280
|
+
const out = [];
|
|
1281
|
+
const sealed = (0, renditionManifest_1.vehicleRenditionManifestProblems)(root, variants);
|
|
1282
|
+
if (sealed.length)
|
|
1283
|
+
out.push(`${v}'s sealed vehicle root draws bytes it does not seal, so owners would see another car: ${sealed.join('; ')} — ${renditionManifest_1.RENDITION_FIX}`);
|
|
1284
|
+
const cand = candidatePackage ? (0, renditionManifest_1.vehicleRenditionManifestProblems)(candidatePackage, variants) : [];
|
|
1285
|
+
if (cand.length)
|
|
1286
|
+
out.push(`the --vehicle-package draws bytes ${v} does not seal: ${cand.join('; ')} — ${renditionManifest_1.RENDITION_FIX}`);
|
|
1287
|
+
return out;
|
|
1288
|
+
}
|
|
1289
|
+
function renditionPlan(variants, defaultCid, root) {
|
|
1290
|
+
const tiers = (root?.mesh?.visual?.tiers ?? {});
|
|
1291
|
+
const rank = { full: 3, simplified: 2, hidden: 0 };
|
|
1292
|
+
const order = ['medium', 'high', 'low', 'ultra', 'minimal'];
|
|
1293
|
+
const tierFor = (id) => {
|
|
1294
|
+
const c = Object.entries(tiers).filter(([, v]) => v?.renditionId === id).map(([k, v]) => ({ k, r: rank[String(v.interior)] ?? 1, o: order.indexOf(k) }));
|
|
1295
|
+
c.sort((a, b) => b.r - a.r || (a.o < 0 ? 99 : a.o) - (b.o < 0 ? 99 : b.o));
|
|
1296
|
+
return c[0]?.k ?? null;
|
|
1297
|
+
};
|
|
1298
|
+
const models = variants.filter((v) => /^vehicle\.model\.web(\.|$)/.test(v.role) && v.cid);
|
|
1299
|
+
const declared = models.map((v) => ({ id: v.id, role: v.role, cid: v.cid, tier: tierFor(v.id) }));
|
|
1300
|
+
const dhex = (0, resolve_1.hexOf)(defaultCid);
|
|
1301
|
+
const def = declared.find((v) => (0, resolve_1.hexOf)(v.cid) === dhex) ?? null;
|
|
1302
|
+
const errors = [];
|
|
1303
|
+
if (models.length && !def)
|
|
1304
|
+
errors.push(`the gate's model ${defaultCid ?? 'none'} is none of the declared model renditions (${models.map((m) => m.id).join(', ')})`);
|
|
1305
|
+
return { default: def, others: declared.filter((v) => v !== def), declared, errors };
|
|
1306
|
+
}
|
|
1307
|
+
// ======================================================================================================================
|
|
1308
|
+
/** Wait for the world frame to present the vehicle, bailing early when the shell refuses the trial. */
|
|
1309
|
+
async function waitPresented(page, timeoutMs, fault = () => null) {
|
|
1310
|
+
const t0 = Date.now();
|
|
1311
|
+
const probe = () => {
|
|
1312
|
+
const rows = globalThis.__assetGym?.mp?.portableVehicles?.presentationRows?.() ?? [];
|
|
1313
|
+
return rows.length > 0 && rows[0].entity?.object3d?.children?.length > 0;
|
|
1314
|
+
};
|
|
1315
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
1316
|
+
for (const f of page.frames())
|
|
1317
|
+
if (await f.evaluate(probe).catch(() => false))
|
|
1318
|
+
return { frame: f, reason: 'presented' };
|
|
1319
|
+
// A composition throw or an uncaught page error before presentation means the car will never appear: say so now,
|
|
1320
|
+
// with the message, instead of waiting out the whole timeout. The reason says "refused", so the load is not retried.
|
|
1321
|
+
const failure = fault();
|
|
1322
|
+
if (failure)
|
|
1323
|
+
return { frame: null, reason: failure };
|
|
1324
|
+
const refusal = await page.evaluate(() => {
|
|
1325
|
+
const t = (globalThis.document?.body?.innerText ?? '');
|
|
1326
|
+
const m = /item trial unavailable[^\n]*\n?[^\n]*|can['’]t be tried[^\n]*|sign in to try[^\n]*/i.exec(t);
|
|
1327
|
+
return m ? m[0].replace(/\s+/g, ' ').trim() : null;
|
|
1328
|
+
}).catch(() => null);
|
|
1329
|
+
if (refusal)
|
|
1330
|
+
return { frame: null, reason: `Try Now refused: "${refusal}"` };
|
|
1331
|
+
await sleep(750);
|
|
1332
|
+
}
|
|
1333
|
+
return { frame: null, reason: `timed out after ${Math.round(timeoutMs / 1000)} s` };
|
|
1334
|
+
}
|
|
1335
|
+
function engineVersionFrom(urls) {
|
|
1336
|
+
for (const u of urls) {
|
|
1337
|
+
const m = /\/humanoid-character\/runtime\/([^/]+)\/index\.js/.exec(u) ?? /\/packages\/[^/]+\/([0-9][^/]*)\/code\/index\.js/.exec(u);
|
|
1338
|
+
if (m)
|
|
1339
|
+
return decodeURIComponent(m[1]);
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
async function hideOverlays(page) {
|
|
1344
|
+
for (const f of page.frames())
|
|
1345
|
+
await f.evaluate(vehicleProbe_1.HIDE_OVERLAYS).catch(() => undefined);
|
|
1346
|
+
}
|
|
1347
|
+
/** Wheel table from a pull: drawn geometry, physics hub, arch + penetration, ride height. */
|
|
1348
|
+
function buildWheelRows(pull) {
|
|
1349
|
+
const carX = pull.wheels.length ? pull.wheels.reduce((s, w) => s + (w.nodePos?.[0] ?? w.mount[0]), 0) / pull.wheels.length : 0;
|
|
1350
|
+
// move each drawn wheel (and its physics hub) from the pull instant to the median compression of the window
|
|
1351
|
+
const shiftY = pull.wheels.map((w, i) => {
|
|
1352
|
+
const med = pull.compressionWindow?.[i]?.median;
|
|
1353
|
+
return med !== null && med !== undefined && w.compression !== null ? med - w.compression : 0;
|
|
1354
|
+
});
|
|
1355
|
+
const geos = pull.wheels.map((w, i) => {
|
|
1356
|
+
if (!w.axleNode)
|
|
1357
|
+
return null;
|
|
1358
|
+
const pts = shiftY[i] ? w.points.map((v, k) => (k % 3 === 1 ? v + shiftY[i] : v)) : w.points;
|
|
1359
|
+
return (0, vehicleMath_1.wheelGeometry)(pts, w.axleNode);
|
|
1360
|
+
});
|
|
1361
|
+
const plane = (0, vehicleMath_1.contactPlane)(geos.map((g, i) => (g ? { centre: g.centre, radius: g.radius, axle: g.axle } : null)).filter((x) => !!x)
|
|
1362
|
+
.concat(geos.every((g) => !g) ? pull.wheels.map((w) => ({ centre: w.nodePos ?? w.mount, radius: w.radius, axle: [1, 0, 0] })) : []));
|
|
1363
|
+
const ride = (0, vehicleMath_1.rideHeight)(pull.body, plane);
|
|
1364
|
+
const rows = pull.wheels.map((w, i) => {
|
|
1365
|
+
const g = geos[i];
|
|
1366
|
+
const comp = (w.compression ?? 0) + shiftY[i];
|
|
1367
|
+
const hub = [w.mount[0], w.mount[1] - ((w.restLength ?? 0) - comp), w.mount[2]];
|
|
1368
|
+
const outward = g ? (Math.sign(g.axle[0] * (g.centre[0] - carX)) || 1) : 1;
|
|
1369
|
+
// judged on the body SURFACE around the wheel (triangle samples), not only its vertices
|
|
1370
|
+
const arch = g ? (0, vehicleMath_1.archAnalysis)((0, vehicleMath_1.sampleTriangles)(pull.bodyTris, (0, vehicleMath_1.wheelNeighbourhood)(g)), g, outward) : null;
|
|
1371
|
+
const contactY = g ? g.centre[1] - g.radius : null;
|
|
1372
|
+
const local = g ? (0, vehicleMath_1.rideHeight)(pull.body, [contactY, 0, 0], (p) => Math.abs(p[2] - g.centre[2]) < 0.6 && Math.sign(p[0] - carX) === Math.sign(g.centre[0] - carX)) : null;
|
|
1373
|
+
return {
|
|
1374
|
+
id: w.id, hub: hub.map((v) => rd(v, 10000)), node: w.nodePos ? w.nodePos.map((v) => rd(v, 10000)) : null,
|
|
1375
|
+
drawnCentre: g ? g.centre : null, delta: g ? (0, vehicleMath_1.sub)(g.centre, hub).map((v) => rd(v, 10000)) : null,
|
|
1376
|
+
radius: g ? g.radius : null, physicsRadius: w.radius, rollRadius: (0, vehicleClearance_1.wheelRollingRadius)({ radius: w.radius, axle: w.axle ?? null }, pull.tyreByAxle ?? null), axle: w.axle ?? null, width: g ? g.width : null, axleAgreementDeg: g ? g.axleAgreementDeg : null,
|
|
1377
|
+
meshes: w.meshes, penetrationCount: arch?.penetrationCount ?? null, maxPenetrationM: arch?.maxPenetrationM ?? null, penetrationMeshes: arch?.penetrationMeshes ?? [],
|
|
1378
|
+
archGapM: arch?.archGapM ?? null, archGapTopM: arch?.archGapTopM ?? null, archCutoutTopM: arch?.archCutoutTopM ?? null, archGapAtDeg: arch?.archGapAtDeg ?? null, archGapMesh: arch?.archGapMesh ?? null,
|
|
1379
|
+
rideHeightLocalM: local?.minM ?? null,
|
|
1380
|
+
compression: rd(comp, 10000), compressionWindow: pull.compressionWindow?.[i] ?? null,
|
|
1381
|
+
};
|
|
1382
|
+
});
|
|
1383
|
+
const duplicates = [];
|
|
1384
|
+
for (const s of pull.strays) {
|
|
1385
|
+
if (!s.centre)
|
|
1386
|
+
continue;
|
|
1387
|
+
for (const r of rows) {
|
|
1388
|
+
const c = r.drawnCentre ?? r.hub;
|
|
1389
|
+
const d = (0, vehicleMath_1.len)((0, vehicleMath_1.sub)(s.centre, c));
|
|
1390
|
+
if (d < vehicleMath_1.VEHICLE_QA_THRESHOLDS.duplicateHubM)
|
|
1391
|
+
duplicates.push({ mesh: s.mesh, nearWheel: r.id, distM: rd(d, 1000) });
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return { rows, duplicates, ride, plane: plane.map((v) => rd(v, 10000)) };
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Board the driver seat with the real E key.
|
|
1398
|
+
*
|
|
1399
|
+
* Why not just teleport next to the door: the room's movement-plausibility gate (helix-colyseus-server
|
|
1400
|
+
* `MAX_SPEED_MPS` = 60 m/s per accepted frame) rejects a 16 m client-side jump and snaps the avatar back to the
|
|
1401
|
+
* spawn a moment later. The seat request is judged by the SERVER, so E pressed in that window is refused, and
|
|
1402
|
+
* `wouldBoard` reads null once the snap-back lands. Measured 2026-10-02: 1 of 4 loads boarded on the first try
|
|
1403
|
+
* that way. So the avatar WALKS there in plausible steps (≈8 m/s), stands still until its position holds, and only
|
|
1404
|
+
* then presses E — and each failed attempt is diagnosed (snapped back / out of reach / refused) in the trace.
|
|
1405
|
+
*/
|
|
1406
|
+
async function boardDriver(page, frame, driverSeat) {
|
|
1407
|
+
const ready = await pollFrame(frame, `(() => Array.isArray(globalThis.__assetGym?.mp?.seatEvidence?.()?.local?.atWorld))()`, 30_000);
|
|
1408
|
+
if (!ready)
|
|
1409
|
+
return { ok: false, reason: 'seat evidence never became available', tries: 0 };
|
|
1410
|
+
// stand outside the driver's door: the driver seat's lateral side, 1.7 m from the centreline
|
|
1411
|
+
const seat = await frame.evaluate((name) => {
|
|
1412
|
+
const P = globalThis.__vqaVeh;
|
|
1413
|
+
const r = P.root();
|
|
1414
|
+
r.updateMatrixWorld(true);
|
|
1415
|
+
let o = null;
|
|
1416
|
+
// by the manifest key when it names a node, else by the driver's cabin seat id (a legacy "driver" key names no node)
|
|
1417
|
+
r.traverse((x) => { if (!o && name && x.name === name && !x.isMesh)
|
|
1418
|
+
o = x; });
|
|
1419
|
+
if (!o)
|
|
1420
|
+
r.traverse((x) => { if (!o && x.name === 'seat_dside_f' && !x.isMesh)
|
|
1421
|
+
o = x; });
|
|
1422
|
+
return o ? P.bodyPos(o) : null;
|
|
1423
|
+
}, driverSeat);
|
|
1424
|
+
const side = seat ? (Math.sign(seat[0]) || 1) : 1;
|
|
1425
|
+
const local = [side * 1.7, 0, seat ? seat[2] : 0.2];
|
|
1426
|
+
const read = () => frame.evaluate(() => {
|
|
1427
|
+
const l = globalThis.__assetGym.mp.seatEvidence().local;
|
|
1428
|
+
const d = (l?.seats ?? []).find((x) => x.role === 'driver');
|
|
1429
|
+
return { at: l?.atWorld ?? null, would: l?.wouldBoard?.role ?? null, seated: l?.seated ? { seatId: l.seated.seatId, role: l.seated.role } : null, driverDist: d?.distanceM ?? null, refusal: d?.refusal ?? null };
|
|
1430
|
+
});
|
|
1431
|
+
// walk in steps the movement gate believes; returns the target in world space
|
|
1432
|
+
const walk = () => frame.evaluate(async (l) => {
|
|
1433
|
+
const mp = globalThis.__assetGym.mp;
|
|
1434
|
+
const e = mp.portableVehicles.presentationRows()[0].entity;
|
|
1435
|
+
const tgt = e.object3d.localToWorld(new e.object3d.position.constructor(...l));
|
|
1436
|
+
const from = mp.seatEvidence().local.atWorld;
|
|
1437
|
+
const y = from[1] + 0.05;
|
|
1438
|
+
const dist = Math.hypot(tgt.x - from[0], tgt.z - from[2]);
|
|
1439
|
+
const n = Math.max(1, Math.ceil(dist / 0.4));
|
|
1440
|
+
for (let i = 1; i <= n; i++) {
|
|
1441
|
+
const k = i / n;
|
|
1442
|
+
mp.local.services.body.teleport({ x: from[0] + (tgt.x - from[0]) * k, y, z: from[2] + (tgt.z - from[2]) * k });
|
|
1443
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1444
|
+
}
|
|
1445
|
+
return [tgt.x, y, tgt.z];
|
|
1446
|
+
}, local);
|
|
1447
|
+
const t0 = Date.now();
|
|
1448
|
+
const trace = [];
|
|
1449
|
+
let last = await read();
|
|
1450
|
+
let tries = 0;
|
|
1451
|
+
for (let i = 0; i < 6 && !last.seated; i++) {
|
|
1452
|
+
tries = i + 1;
|
|
1453
|
+
const tgt = await walk();
|
|
1454
|
+
// hold still until the server has accepted the position (no snap-back for 1 s)
|
|
1455
|
+
let held = 0;
|
|
1456
|
+
for (let k = 0; k < 12 && held < 3; k++) {
|
|
1457
|
+
await sleep(350);
|
|
1458
|
+
last = await read();
|
|
1459
|
+
const off = last.at ? Math.hypot(last.at[0] - tgt[0], last.at[2] - tgt[2]) : Infinity;
|
|
1460
|
+
held = off < 0.6 ? held + 1 : 0;
|
|
1461
|
+
if (off > 3)
|
|
1462
|
+
break; // snapped back: walk again
|
|
1463
|
+
}
|
|
1464
|
+
const rec = { t: +((Date.now() - t0) / 1000).toFixed(1), held, at: last.at?.map((v) => +v.toFixed(2)), would: last.would, driverDist: last.driverDist === null ? null : +last.driverDist.toFixed(2), refusal: last.refusal };
|
|
1465
|
+
if (held < 3 || last.would !== 'driver') {
|
|
1466
|
+
rec.outcome = held < 3 ? 'position not held (server snap-back)' : `would board ${last.would ?? 'nothing'}`;
|
|
1467
|
+
trace.push(rec);
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
await page.keyboard.press('KeyE');
|
|
1471
|
+
for (let k = 0; k < 12 && !last.seated; k++) {
|
|
1472
|
+
await sleep(250);
|
|
1473
|
+
last = await read();
|
|
1474
|
+
}
|
|
1475
|
+
rec.outcome = last.seated ? `seated ${last.seated.role}` : 'E pressed, not seated';
|
|
1476
|
+
trace.push(rec);
|
|
1477
|
+
}
|
|
1478
|
+
if (!last.seated)
|
|
1479
|
+
return { ok: false, reason: `not seated after ${tries} walk-and-board attempts (last: ${JSON.stringify(trace[trace.length - 1] ?? null)})`, tries, trace };
|
|
1480
|
+
if (last.seated.role !== 'driver')
|
|
1481
|
+
return { ok: false, reason: `seated as ${last.seated.role}, not driver`, seated: last.seated, tries, trace };
|
|
1482
|
+
return { ok: true, reason: 'seated', seated: last.seated, tries, trace };
|
|
1483
|
+
}
|
|
1484
|
+
async function pollFrame(frame, src, timeoutMs, stepMs = 400) {
|
|
1485
|
+
const t0 = Date.now();
|
|
1486
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
1487
|
+
if (await frame.evaluate(src).catch(() => false))
|
|
1488
|
+
return true;
|
|
1489
|
+
await sleep(stepMs);
|
|
1490
|
+
}
|
|
1491
|
+
return false;
|
|
1492
|
+
}
|
|
1493
|
+
/** All wheels grounded and the body still (position drift < 5 mm over 600 ms). */
|
|
1494
|
+
async function waitSettled(frame, timeoutMs) {
|
|
1495
|
+
const t0 = Date.now();
|
|
1496
|
+
const read = () => frame.evaluate(() => { const s = globalThis.__vqaVeh.e().controller.getState(); return { g: (s.wheels ?? []).map((w) => !!w.grounded), p: [s.position.x, s.position.y, s.position.z], speed: s.speed }; });
|
|
1497
|
+
let prev = await read();
|
|
1498
|
+
let last = prev;
|
|
1499
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
1500
|
+
await sleep(600);
|
|
1501
|
+
last = await read();
|
|
1502
|
+
const drift = Math.hypot(last.p[0] - prev.p[0], last.p[1] - prev.p[1], last.p[2] - prev.p[2]);
|
|
1503
|
+
const grounded = last.g.length > 0 && last.g.every(Boolean);
|
|
1504
|
+
if (grounded && drift < 0.005)
|
|
1505
|
+
return { grounded: true, still: true, seconds: (Date.now() - t0) / 1000, last };
|
|
1506
|
+
prev = last;
|
|
1507
|
+
}
|
|
1508
|
+
return { grounded: last.g.length > 0 && last.g.every(Boolean), still: false, seconds: (Date.now() - t0) / 1000, last };
|
|
1509
|
+
}
|
|
1510
|
+
async function setCameraMode(page, frame, want) {
|
|
1511
|
+
const mode = () => frame.evaluate(() => globalThis.__assetGym.mp.local.services.camera?.mode ?? null).catch(() => null);
|
|
1512
|
+
let m = await mode();
|
|
1513
|
+
let presses = 0;
|
|
1514
|
+
for (; presses < 4 && m !== want; presses++) {
|
|
1515
|
+
await page.keyboard.press('KeyT');
|
|
1516
|
+
await page.waitForTimeout(700);
|
|
1517
|
+
m = await mode();
|
|
1518
|
+
}
|
|
1519
|
+
return { mode: m, presses };
|
|
1520
|
+
}
|
|
1521
|
+
async function sweepPanel(page, frame, p, car, shot, setCam) {
|
|
1522
|
+
const main = p.nodes[0];
|
|
1523
|
+
const angleNow = async (closedQ) => {
|
|
1524
|
+
const pose = await frame.evaluate((n) => globalThis.__vqaVeh.pose([n]), main);
|
|
1525
|
+
return pose[main] ? (0, vehicleMath_1.signedAngleAboutDeg)(closedQ, pose[main].q, p.hinge.axis) : null;
|
|
1526
|
+
};
|
|
1527
|
+
const durable = () => frame.evaluate((s) => globalThis.__vqaVeh.durable()[s], p.signal);
|
|
1528
|
+
const rels = () => frame.evaluate((nodes) => Object.fromEntries(nodes.slice(1).map((n) => [n, globalThis.__vqaVeh.rel(nodes[0], n)])), p.nodes);
|
|
1529
|
+
const closedPose = await frame.evaluate((n) => globalThis.__vqaVeh.pose([n]), main);
|
|
1530
|
+
if (!closedPose[main])
|
|
1531
|
+
throw new Error(`panel node ${main} not found in the scene`);
|
|
1532
|
+
const closedQ = closedPose[main].q;
|
|
1533
|
+
const closedDurable = await durable();
|
|
1534
|
+
const closedRel = await rels();
|
|
1535
|
+
const box = await frame.evaluate((n) => globalThis.__vqaVeh.nodeBox(n), p.nodes);
|
|
1536
|
+
/**
|
|
1537
|
+
* Follow the panel until its travel finishes, never on a fixed sleep (lids and physical doors run slower on a
|
|
1538
|
+
* loaded machine). Re-issues the toggle when the runtime's own state never changed and nothing moved, and reports
|
|
1539
|
+
* WHY it stopped: reached the target, stopped short / never moved although the runtime state flipped (a runtime
|
|
1540
|
+
* defect), or the toggle was never accepted.
|
|
1541
|
+
*/
|
|
1542
|
+
const travel = async (target, tol, want, toggle) => {
|
|
1543
|
+
const start = await angleNow(closedQ);
|
|
1544
|
+
const t0 = Date.now();
|
|
1545
|
+
const trace = [];
|
|
1546
|
+
let prev = start, stable = 0, reissued = 0, lastToggleAt = t0, a = start, d = undefined;
|
|
1547
|
+
while (Date.now() - t0 < 9000) {
|
|
1548
|
+
await sleep(150);
|
|
1549
|
+
a = await angleNow(closedQ);
|
|
1550
|
+
d = await durable();
|
|
1551
|
+
const t = +((Date.now() - t0) / 1000).toFixed(2);
|
|
1552
|
+
if (trace.length < 60)
|
|
1553
|
+
trace.push([t, a === null ? null : +a.toFixed(1)]);
|
|
1554
|
+
if (a !== null && prev !== null && Math.abs(a - prev) < 0.25)
|
|
1555
|
+
stable++;
|
|
1556
|
+
else
|
|
1557
|
+
stable = 0;
|
|
1558
|
+
prev = a;
|
|
1559
|
+
const moved = a !== null && start !== null ? Math.abs(a - start) : 0;
|
|
1560
|
+
if (a !== null && Math.abs(a - target) <= tol && stable >= 2)
|
|
1561
|
+
return { deg: a, status: 'reached', reissued, seconds: t, trace };
|
|
1562
|
+
if (toggle && typeof d === 'boolean' && d !== want && moved < 2 && Date.now() - lastToggleAt > 1500 && reissued < 2) {
|
|
1563
|
+
await toggle();
|
|
1564
|
+
reissued++;
|
|
1565
|
+
lastToggleAt = Date.now();
|
|
1566
|
+
}
|
|
1567
|
+
if (moved >= 2 && stable >= 16)
|
|
1568
|
+
break; // moved, then sat still for 2.4 s away from the target
|
|
1569
|
+
}
|
|
1570
|
+
const moved = a !== null && start !== null ? Math.abs(a - start) : 0;
|
|
1571
|
+
const status = typeof d === 'boolean' && d !== want ? 'toggle-not-accepted' : moved < 2 ? 'state-flipped-but-panel-did-not-move' : 'stopped-short';
|
|
1572
|
+
return { deg: a, status, reissued, seconds: +((Date.now() - t0) / 1000).toFixed(2), trace };
|
|
1573
|
+
};
|
|
1574
|
+
// operate with the default key, verified by the durable flip; otherwise discover the key among the free ones
|
|
1575
|
+
let key = PANEL_KEYS[p.signal] ?? PANEL_KEYS[p.id] ?? null;
|
|
1576
|
+
const tried = [];
|
|
1577
|
+
const press = async (k) => { await page.keyboard.press(k); tried.push(k); };
|
|
1578
|
+
let flipped = false;
|
|
1579
|
+
// The runtime's durable state can lag the key by several hundred ms (measured: a trunk flip landed after 350 ms,
|
|
1580
|
+
// was credited to the NEXT fallback key, and that key — the driver door — was then pressed to "close" the trunk).
|
|
1581
|
+
// So each candidate gets up to 1.5 s, and counts as accepted on the state flip OR on the panel starting to move.
|
|
1582
|
+
for (const k of [key, ...FALLBACK_KEYS.filter((x) => x !== key)].filter((x) => !!x)) {
|
|
1583
|
+
const before = await frame.evaluate(() => globalThis.__vqaVeh.durable());
|
|
1584
|
+
await press(k);
|
|
1585
|
+
let after = before;
|
|
1586
|
+
let movedNow = false;
|
|
1587
|
+
for (let w = 0; w < 10; w++) {
|
|
1588
|
+
await sleep(150);
|
|
1589
|
+
after = await frame.evaluate(() => globalThis.__vqaVeh.durable());
|
|
1590
|
+
const a = await angleNow(closedQ);
|
|
1591
|
+
movedNow = a !== null && Math.abs(a) > 2;
|
|
1592
|
+
if (after[p.signal] !== before[p.signal] || movedNow)
|
|
1593
|
+
break;
|
|
1594
|
+
if (Object.keys(after).some((x) => x !== p.signal && after[x] !== before[x]))
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
if (after[p.signal] !== before[p.signal] || movedNow) {
|
|
1598
|
+
key = k;
|
|
1599
|
+
flipped = true;
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
// the key toggled some OTHER panel: put it back and wait for it to settle before trying the next key
|
|
1603
|
+
const other = Object.keys(after).find((x) => after[x] !== before[x] && /^(door|hood|trunk|boot|bonnet|tailgate|lid)/i.test(x));
|
|
1604
|
+
if (other) {
|
|
1605
|
+
await page.keyboard.press(k);
|
|
1606
|
+
await sleep(1800);
|
|
1607
|
+
}
|
|
1608
|
+
if (tried.length >= 4)
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
const expected = p.hinge.openDeg - p.hinge.closedDeg;
|
|
1612
|
+
const opening = await travel(expected, vehicleMath_1.VEHICLE_QA_THRESHOLDS.panelTolDeg, true, flipped && key ? () => page.keyboard.press(key) : null);
|
|
1613
|
+
const openDeg = opening.deg;
|
|
1614
|
+
const openDurable = await durable();
|
|
1615
|
+
const openRel = await rels();
|
|
1616
|
+
// frame the panel at full open: from outside, on the panel's side, a little above
|
|
1617
|
+
if (box) {
|
|
1618
|
+
const c = [(box.min[0] + box.max[0]) / 2, (box.min[1] + box.max[1]) / 2, (box.min[2] + box.max[2]) / 2];
|
|
1619
|
+
const carC = [(car.min[0] + car.max[0]) / 2, (car.min[1] + car.max[1]) / 2, (car.min[2] + car.max[2]) / 2];
|
|
1620
|
+
const isLid = Math.abs(p.hinge.axis[0]) > 0.7;
|
|
1621
|
+
const dir = isLid ? [0.7, 0.9, Math.sign(c[2] - carC[2]) || 1] : [Math.sign(c[0] - carC[0]) || 1, 0.45, 0.9];
|
|
1622
|
+
const pad = 0.5;
|
|
1623
|
+
await setCam((0, vehicleMath_1.camFit)([box.min[0] - pad, box.min[1] - pad * 0.4, box.min[2] - pad], [box.max[0] + pad, box.max[1] + pad * 0.6, box.max[2] + pad], dir, 38, 1.6, 0.9));
|
|
1624
|
+
}
|
|
1625
|
+
await shot(`${p.id}.open`, `${p.id} (${p.role || 'panel'}) at full open — declared ${p.hinge.openDeg}°, measured ${openDeg === null ? '?' : openDeg.toFixed(1)}°`, 'articulation', 350);
|
|
1626
|
+
let closing = null;
|
|
1627
|
+
if (flipped && key) {
|
|
1628
|
+
await page.keyboard.press(key);
|
|
1629
|
+
closing = await travel(0, vehicleMath_1.VEHICLE_QA_THRESHOLDS.panelReturnTolDeg, false, () => page.keyboard.press(key));
|
|
1630
|
+
}
|
|
1631
|
+
const closedAgainDeg = closing ? closing.deg : await angleNow(closedQ);
|
|
1632
|
+
const againDurable = await durable();
|
|
1633
|
+
const attached = Object.keys(closedRel).map((n) => {
|
|
1634
|
+
const a = closedRel[n], o = openRel[n];
|
|
1635
|
+
if (!a || !o)
|
|
1636
|
+
return { node: n, missing: true };
|
|
1637
|
+
const maxPos = Math.max(...a.p.map((v, i) => Math.abs(o.p[i] - v)));
|
|
1638
|
+
const qd = Math.abs(a.q.reduce((s, v, i) => s + v * o.q[i], 0));
|
|
1639
|
+
return { node: n, maxPosDeltaM: rd(maxPos, 10000), quatDot: rd(qd, 100000) };
|
|
1640
|
+
});
|
|
1641
|
+
return {
|
|
1642
|
+
id: p.id, expectedDeg: p.hinge.openDeg - p.hinge.closedDeg,
|
|
1643
|
+
openDeg: openDeg === null ? null : +openDeg.toFixed(2), closedAgainDeg: closedAgainDeg === null ? null : +closedAgainDeg.toFixed(2),
|
|
1644
|
+
durable: { closed: closedDurable, open: openDurable, closedAgain: againDurable }, attached, key: flipped ? key : null,
|
|
1645
|
+
opening: { status: flipped ? opening.status : 'no key toggled this panel', reissued: opening.reissued, seconds: opening.seconds, trace: opening.trace },
|
|
1646
|
+
closing: closing ? { status: closing.status, reissued: closing.reissued, seconds: closing.seconds, trace: closing.trace } : null,
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
async function steeringSweep(page, frame, rows, shot, setCam, clearCam) {
|
|
1650
|
+
const read = () => frame.evaluate(() => globalThis.__vqaVeh.steering());
|
|
1651
|
+
const centre = await read();
|
|
1652
|
+
const front = rows.find((r) => /lf|fl|front_?l/i.test(r.id)) ?? rows[0];
|
|
1653
|
+
const fc = front.drawnCentre ?? front.hub;
|
|
1654
|
+
const fr = front.radius ?? front.physicsRadius;
|
|
1655
|
+
await page.keyboard.down('KeyA');
|
|
1656
|
+
await page.waitForTimeout(1100);
|
|
1657
|
+
const lock = await read();
|
|
1658
|
+
// the turned front wheel: from ahead and outside, low
|
|
1659
|
+
const out = Math.sign(fc[0]) || 1;
|
|
1660
|
+
await setCam({ pos: [fc[0] + out * 1.25, fc[1] + 0.35, fc[2] + 1.35], target: [fc[0], fc[1] + fr * 0.2, fc[2]], fov: 38, near: 0.05 });
|
|
1661
|
+
await shot('steering.full_lock', `Full lock (A held): ${front.id} turned`, 'steering', 300);
|
|
1662
|
+
await clearCam();
|
|
1663
|
+
const fp = await setCameraMode(page, frame, 'first-person');
|
|
1664
|
+
await page.waitForTimeout(500);
|
|
1665
|
+
await shot('steering.full_lock.cabin', 'Full lock (A held): steering wheel, driver view', 'steering', 400);
|
|
1666
|
+
const lock2 = await read();
|
|
1667
|
+
await page.keyboard.up('KeyA');
|
|
1668
|
+
await setCameraMode(page, frame, 'third-person');
|
|
1669
|
+
await page.waitForTimeout(800);
|
|
1670
|
+
const wheels = centre.wheels.map((w, i) => {
|
|
1671
|
+
const l = lock.wheels[i];
|
|
1672
|
+
const yaw0 = w.axle ? (0, vehicleMath_1.axleYawDeg)(w.axle) : null, yaw1 = l?.axle ? (0, vehicleMath_1.axleYawDeg)(l.axle) : null;
|
|
1673
|
+
return { id: w.id, steered: w.steered, drawnYawDeg: yaw0 !== null && yaw1 !== null ? +(yaw1 - yaw0).toFixed(2) : null, physicsSteerDeg: l?.physicsSteerRad !== null && l?.physicsSteerRad !== undefined ? +((l.physicsSteerRad * 180) / Math.PI).toFixed(2) : null };
|
|
1674
|
+
});
|
|
1675
|
+
const rimDeg = centre.rimQ && lock.rimQ ? +(0, vehicleMath_1.qAngleDeg)((0, vehicleMath_1.qMul)(lock.rimQ, (0, vehicleMath_1.qInv)(centre.rimQ))).toFixed(1) : null;
|
|
1676
|
+
const steered = wheels.filter((w) => w.steered);
|
|
1677
|
+
const weak = steered.filter((w) => w.drawnYawDeg === null || Math.abs(w.drawnYawDeg) < vehicleMath_1.VEHICLE_QA_THRESHOLDS.steerWheelMinDeg);
|
|
1678
|
+
const disagree = steered.filter((w) => w.drawnYawDeg !== null && w.physicsSteerDeg !== null && Math.abs(Math.abs(w.drawnYawDeg) - Math.abs(w.physicsSteerDeg)) > vehicleMath_1.VEHICLE_QA_THRESHOLDS.steerAgreementTolDeg);
|
|
1679
|
+
const rimOk = rimDeg !== null && rimDeg >= vehicleMath_1.VEHICLE_QA_THRESHOLDS.steeringWheelMinDeg;
|
|
1680
|
+
const pass = steered.length > 0 && weak.length === 0 && disagree.length === 0 && rimOk;
|
|
1681
|
+
const detail = !steered.length ? 'no steered wheels in the live tuning'
|
|
1682
|
+
: weak.length ? `front wheels barely turn at full lock: ${weak.map((w) => `${w.id} ${w.drawnYawDeg}°`).join(', ')}`
|
|
1683
|
+
: disagree.length ? `drawn wheel yaw disagrees with physics steer: ${disagree.map((w) => `${w.id} drawn ${w.drawnYawDeg}° vs ${w.physicsSteerDeg}°`).join(', ')}`
|
|
1684
|
+
: !rimOk ? `steering wheel ${centre.rimNode ?? '(none)'} turned only ${rimDeg ?? '?'}° at full lock`
|
|
1685
|
+
: `full lock: ${steered.map((w) => `${w.id} ${w.drawnYawDeg}°`).join(', ')}; steering wheel ${rimDeg}°`;
|
|
1686
|
+
return { pass, detail, measured: { wheels, steeringWheelDeg: rimDeg, rimNode: centre.rimNode, rimAngleRad: { centre: centre.rimAngleRad, lock: lock.rimAngleRad, lockFirstPerson: lock2.rimAngleRad }, steerInput: lock.steerInput, cameraForCabin: fp } };
|
|
1687
|
+
}
|
|
1688
|
+
/** Scenery boxes around the car (WORLD frame) and the car's body->world matrix, read once per decision. */
|
|
1689
|
+
async function readScenery(frame) {
|
|
1690
|
+
return frame.evaluate((r) => globalThis.__vqaVeh.sceneBoxes(r), 60).catch(() => null);
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Clear distance from the car's bumper (front for dir +1, rear for -1) along its own heading to the first scenery box,
|
|
1694
|
+
* sampled from five points across the bumper face at two heights. Infinity when the grounds report nothing there.
|
|
1695
|
+
*/
|
|
1696
|
+
function bumperClearance(sc, bounds, dirSign) {
|
|
1697
|
+
const z = dirSign > 0 ? bounds.max[2] : bounds.min[2];
|
|
1698
|
+
const w = (bounds.max[0] - bounds.min[0]) / 2 * 0.85;
|
|
1699
|
+
const cx = (bounds.max[0] + bounds.min[0]) / 2;
|
|
1700
|
+
const lo = bounds.min[1] + Math.min(0.25, (bounds.max[1] - bounds.min[1]) * 0.25);
|
|
1701
|
+
const hi = bounds.min[1] + (bounds.max[1] - bounds.min[1]) * 0.6;
|
|
1702
|
+
const origins = [];
|
|
1703
|
+
for (const y of [lo, hi])
|
|
1704
|
+
for (const x of [cx - w, cx - w / 2, cx, cx + w / 2, cx + w])
|
|
1705
|
+
origins.push((0, vehicleClearance_1.applyMatrix)(sc.bodyToWorld, [x, y, z]));
|
|
1706
|
+
const o0 = (0, vehicleClearance_1.applyMatrix)(sc.bodyToWorld, [0, 0, 0]);
|
|
1707
|
+
const o1 = (0, vehicleClearance_1.applyMatrix)(sc.bodyToWorld, [0, 0, dirSign]);
|
|
1708
|
+
const d = [o1[0] - o0[0], 0, o1[2] - o0[2]];
|
|
1709
|
+
const l = Math.hypot(d[0], d[2]) || 1;
|
|
1710
|
+
return (0, vehicleClearance_1.clearanceAlong)(origins, [d[0] / l, 0, d[2] / l], sc.boxes);
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* The drive. Plans against the test grounds' own scenery so it never ends against a wall (F40 round 1: it did, and every
|
|
1714
|
+
* body-frame exterior camera after it was then inside the wall): it measures the clear room behind and ahead, backs up
|
|
1715
|
+
* no further than that room allows, releases the throttle early enough to settle, coast and still stop `marginM` short
|
|
1716
|
+
* of what is ahead, samples the spin once launch wheelspin has died away and only over the free part of the coast, and
|
|
1717
|
+
* finally drives back to where it started (closed loop on position, bounded time), so the occupant phase has the space
|
|
1718
|
+
* the car was spawned in. Stops use the foot brake (the opposite pedal, as a player does) with the handbrake.
|
|
1719
|
+
*/
|
|
1720
|
+
async function driveAndSample(page, frame, rows, shot, clearCam, bounds) {
|
|
1721
|
+
await clearCam();
|
|
1722
|
+
const C = vehicleClearance_1.CLEARANCE_DEFAULTS;
|
|
1723
|
+
const radii = rows.map((r) => r.rollRadius ?? r.physicsRadius);
|
|
1724
|
+
// position (world xz), forward speed, the car's forward direction (world xz) and each wheel's angular velocity
|
|
1725
|
+
const state = () => frame.evaluate(() => {
|
|
1726
|
+
const P = globalThis.__vqaVeh;
|
|
1727
|
+
const e = P.e();
|
|
1728
|
+
const s = e.controller.getState();
|
|
1729
|
+
const r = P.root();
|
|
1730
|
+
const V = P.V();
|
|
1731
|
+
r.updateMatrixWorld(true);
|
|
1732
|
+
const a = r.localToWorld(new V(0, 0, 0));
|
|
1733
|
+
const b = r.localToWorld(new V(0, 0, 1));
|
|
1734
|
+
// every non-finite number in the vehicle state and its telemetry (a NaN froze the R34 in Try Now)
|
|
1735
|
+
const nf = [];
|
|
1736
|
+
const walk = (v, at, d) => {
|
|
1737
|
+
if (nf.length >= 12 || d > 4)
|
|
1738
|
+
return;
|
|
1739
|
+
if (typeof v === 'number') {
|
|
1740
|
+
if (!Number.isFinite(v))
|
|
1741
|
+
nf.push(`${at}=${v}`);
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
if (!v || typeof v !== 'object')
|
|
1745
|
+
return;
|
|
1746
|
+
if (Array.isArray(v)) {
|
|
1747
|
+
v.slice(0, 32).forEach((x, i) => walk(x, `${at}[${i}]`, d + 1));
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
for (const k of Object.keys(v).slice(0, 64))
|
|
1751
|
+
walk(v[k], at ? `${at}.${k}` : k, d + 1);
|
|
1752
|
+
};
|
|
1753
|
+
walk(s, 'state', 0);
|
|
1754
|
+
try {
|
|
1755
|
+
if (typeof e.telemetry === 'function')
|
|
1756
|
+
walk(e.telemetry(), 'telemetry', 0);
|
|
1757
|
+
}
|
|
1758
|
+
catch { /* telemetry is optional */ }
|
|
1759
|
+
return { fwd: s.forwardSpeed, gear: s.gear, p: [s.position.x, s.position.z], h: [b.x - a.x, b.z - a.z], w: (s.wheels || []).map((w) => (typeof w.angularVelocity === 'number' ? w.angularVelocity : null)), nf };
|
|
1760
|
+
});
|
|
1761
|
+
// the World's audio graph as the drive sees it (browser.ts installHooks records contexts and non-finite AudioParam writes)
|
|
1762
|
+
const audioNow = () => frame.evaluate(() => {
|
|
1763
|
+
const A = globalThis.__vqaAudio;
|
|
1764
|
+
let issues = [];
|
|
1765
|
+
let fitted = [];
|
|
1766
|
+
try {
|
|
1767
|
+
const au = globalThis.__assetGym?.mp?.portableVehicleEvidence?.().vehicles?.[0]?.audio;
|
|
1768
|
+
issues = ((au?.issues) ?? []).map((i) => String(i?.reason ?? i));
|
|
1769
|
+
fitted = ((au?.fittedRows) ?? []).map((r) => `${r.id}${r.slot ? `:${r.slot}` : ''}`);
|
|
1770
|
+
}
|
|
1771
|
+
catch { /* evidence is optional */ }
|
|
1772
|
+
return { ctx: (A?.contexts ?? []).map((c) => ({ state: String(c.state), time: Number(c.currentTime) })), n: Number(A?.nonFiniteCount ?? 0), w: Number(A?.writes ?? 0), ex: (A?.nonFinite ?? []).slice(0, 5), issues, fitted };
|
|
1773
|
+
}).catch(() => ({ ctx: [], n: 0, w: 0, ex: [], issues: [], fitted: [] }));
|
|
1774
|
+
const pageErrors = [];
|
|
1775
|
+
let watchErrors = false;
|
|
1776
|
+
const onPageError = (err) => { if (watchErrors && pageErrors.length < 10)
|
|
1777
|
+
pageErrors.push(String(err?.message ?? err).slice(0, 300)); };
|
|
1778
|
+
const onConsole = (m) => { if (m.type() === 'error' && /non-finite|setValueAtTime|AudioParam|\bNaN\b/i.test(m.text()) && pageErrors.length < 10)
|
|
1779
|
+
pageErrors.push(m.text().slice(0, 300)); };
|
|
1780
|
+
page.on('pageerror', onPageError);
|
|
1781
|
+
page.on('console', onConsole);
|
|
1782
|
+
try {
|
|
1783
|
+
const brakeTo0 = async () => {
|
|
1784
|
+
// the foot brake is the pedal against the motion (S going forward, W going backwards), with the handbrake
|
|
1785
|
+
for (let i = 0; i < 40; i++) {
|
|
1786
|
+
const s = await state();
|
|
1787
|
+
if (Math.abs(s.fwd) < 0.3)
|
|
1788
|
+
break;
|
|
1789
|
+
const key = s.fwd > 0 ? 'KeyS' : 'KeyW';
|
|
1790
|
+
await page.keyboard.down(key);
|
|
1791
|
+
await page.keyboard.down('Space');
|
|
1792
|
+
await page.waitForTimeout(100);
|
|
1793
|
+
await page.keyboard.up(key);
|
|
1794
|
+
await page.keyboard.up('Space');
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
const trace = [];
|
|
1798
|
+
const short = (s) => ({ fwd: +s.fwd.toFixed(2), gear: s.gear, p: s.p.map((v) => +v.toFixed(3)) });
|
|
1799
|
+
const p0 = await state();
|
|
1800
|
+
const hl = Math.hypot(p0.h[0], p0.h[1]) || 1;
|
|
1801
|
+
const heading = [p0.h[0] / hl, p0.h[1] / hl];
|
|
1802
|
+
/** Displacement from the start along the START heading (+ = ahead of it), and across it. */
|
|
1803
|
+
const along = (p) => (p[0] - p0.p[0]) * heading[0] + (p[1] - p0.p[1]) * heading[1];
|
|
1804
|
+
const across = (p) => -(p[0] - p0.p[0]) * heading[1] + (p[1] - p0.p[1]) * heading[0];
|
|
1805
|
+
const room = async () => {
|
|
1806
|
+
const sc = bounds ? await readScenery(frame) : null;
|
|
1807
|
+
if (!sc || !bounds)
|
|
1808
|
+
return { ahead: { distanceM: Infinity, box: null, skipped: 0 }, behind: { distanceM: Infinity, box: null, skipped: 0 }, boxes: 0 };
|
|
1809
|
+
return { ahead: bumperClearance(sc, bounds, 1), behind: bumperClearance(sc, bounds, -1), boxes: sc.boxes.length };
|
|
1810
|
+
};
|
|
1811
|
+
const fin = (v) => (Number.isFinite(v) ? +v.toFixed(2) : null);
|
|
1812
|
+
const room0 = await room();
|
|
1813
|
+
trace.push({ phase: 'start', ...short(p0), clearAheadM: fin(room0.ahead.distanceM), clearAheadBox: room0.ahead.box, clearBehindM: fin(room0.behind.distanceM), clearBehindBox: room0.behind.box, sceneryBoxes: room0.boxes });
|
|
1814
|
+
// 1. back up to make room ahead: up to REVERSE_WANT_M, never closer than the margin to what is behind
|
|
1815
|
+
// Use the room behind (bounded by it, minus the margin): the coast needs its whole window at speed.
|
|
1816
|
+
const REVERSE_WANT_M = 20;
|
|
1817
|
+
{
|
|
1818
|
+
const t0 = Date.now();
|
|
1819
|
+
await page.keyboard.down('KeyS');
|
|
1820
|
+
for (;;) {
|
|
1821
|
+
await page.waitForTimeout(80);
|
|
1822
|
+
const s = await state();
|
|
1823
|
+
if (Date.now() - t0 > 5000 || (0, vehicleClearance_1.shouldStopReversing)({ travelledM: Math.max(0, -along(s.p)), speedMps: s.fwd, clearM: room0.behind.distanceM, wantM: REVERSE_WANT_M }))
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
await page.keyboard.up('KeyS');
|
|
1827
|
+
}
|
|
1828
|
+
trace.push({ phase: 'reversed', ...short(await state()) });
|
|
1829
|
+
await brakeTo0();
|
|
1830
|
+
await page.waitForTimeout(300);
|
|
1831
|
+
// 2. throttle, released early enough to settle, coast and still stop short of what is ahead
|
|
1832
|
+
const room1 = await room();
|
|
1833
|
+
const s1 = await state();
|
|
1834
|
+
const from = along(s1.p);
|
|
1835
|
+
trace.push({ phase: 'stopped', ...short(s1), alongM: +from.toFixed(2), clearAheadM: fin(room1.ahead.distanceM), clearAheadBox: room1.ahead.box });
|
|
1836
|
+
// Throttle with the driver's own traction control: a powerful car spins its driven wheels from rest, and a wheel
|
|
1837
|
+
// released spinning keeps pushing the car on (measured: the F40 went from 6.7 to 8.2 m/s AFTER the release), so W is
|
|
1838
|
+
// lifted while any wheel slips more than TC_SLIP and pressed again once it grips.
|
|
1839
|
+
const TC_SLIP = 0.25;
|
|
1840
|
+
let wDown = false;
|
|
1841
|
+
const setW = async (down) => { if (down === wDown)
|
|
1842
|
+
return; if (down)
|
|
1843
|
+
await page.keyboard.down('KeyW');
|
|
1844
|
+
else
|
|
1845
|
+
await page.keyboard.up('KeyW'); wDown = down; };
|
|
1846
|
+
const audio0 = await audioNow();
|
|
1847
|
+
watchErrors = true;
|
|
1848
|
+
// Debug proof only (the caller records a harness error): from the throttle on, every AudioParam write carries NaN.
|
|
1849
|
+
if (process.env.HELIX_VQA_FAULT_AUDIO_NAN)
|
|
1850
|
+
await frame.evaluate(() => { const A = globalThis.__vqaAudio; if (A)
|
|
1851
|
+
A.injectNaN = true; });
|
|
1852
|
+
await setW(true);
|
|
1853
|
+
let peak = 0;
|
|
1854
|
+
let releasedFor = 'speed';
|
|
1855
|
+
let tcLifts = 0;
|
|
1856
|
+
const t0 = Date.now();
|
|
1857
|
+
const alive = [];
|
|
1858
|
+
for (;;) {
|
|
1859
|
+
await page.waitForTimeout(40);
|
|
1860
|
+
const s = await state();
|
|
1861
|
+
alive.push({ t: (Date.now() - t0) / 1000, fwd: s.fwd, p: s.p, w: s.w, nonFinite: s.nf });
|
|
1862
|
+
if (s.nf.length && Date.now() - t0 > 1500) {
|
|
1863
|
+
releasedFor = 'non-finite state';
|
|
1864
|
+
break;
|
|
1865
|
+
}
|
|
1866
|
+
peak = Math.max(peak, Number.isFinite(s.fwd) ? s.fwd : 0);
|
|
1867
|
+
if (s.fwd > 9)
|
|
1868
|
+
break;
|
|
1869
|
+
if (Date.now() - t0 > 4000) {
|
|
1870
|
+
releasedFor = 'time';
|
|
1871
|
+
break;
|
|
1872
|
+
}
|
|
1873
|
+
if ((0, vehicleClearance_1.shouldRelease)({ travelledM: along(s.p) - from, speedMps: s.fwd, clearM: room1.ahead.distanceM })) {
|
|
1874
|
+
releasedFor = 'room';
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1877
|
+
const slip = (0, vehicleClearance_1.worstSlip)(s.fwd, s.w, radii);
|
|
1878
|
+
if (slip !== null && slip > TC_SLIP) {
|
|
1879
|
+
if (wDown)
|
|
1880
|
+
tcLifts++;
|
|
1881
|
+
await setW(false);
|
|
1882
|
+
}
|
|
1883
|
+
else
|
|
1884
|
+
await setW(true);
|
|
1885
|
+
}
|
|
1886
|
+
await setW(false);
|
|
1887
|
+
const throttleSeconds = (Date.now() - t0) / 1000;
|
|
1888
|
+
watchErrors = false;
|
|
1889
|
+
if (process.env.HELIX_VQA_FAULT_AUDIO_NAN)
|
|
1890
|
+
await frame.evaluate(() => { const A = globalThis.__vqaAudio; if (A)
|
|
1891
|
+
A.injectNaN = false; }).catch(() => undefined);
|
|
1892
|
+
const audio1 = await audioNow();
|
|
1893
|
+
const aliveFacts = {
|
|
1894
|
+
throttleSeconds, samples: alive, pageErrors: [...pageErrors],
|
|
1895
|
+
audio: { before: audio0.ctx, after: audio1.ctx, nonFiniteWrites: Math.max(0, audio1.n - audio0.n), nonFiniteExamples: audio1.ex.map((x) => ({ method: String(x.method), value: String(x.value) })), issues: audio1.issues, fittedSoundRows: audio1.fitted, paramWrites: Math.max(0, audio1.w - audio0.w) },
|
|
1896
|
+
};
|
|
1897
|
+
const sr = await state();
|
|
1898
|
+
trace.push({ phase: 'throttle-released', releasedFor, tractionLifts: tcLifts, ...short(sr), travelledM: +(along(sr.p) - from).toFixed(2), slip: (0, vehicleClearance_1.worstSlip)(sr.fwd, sr.w, radii) });
|
|
1899
|
+
// The coast is sampled once every wheel rolls with the road (bounded wait; the traction control above keeps it short).
|
|
1900
|
+
const tr = Date.now();
|
|
1901
|
+
{
|
|
1902
|
+
let slip = null;
|
|
1903
|
+
for (;;) {
|
|
1904
|
+
const s = await state();
|
|
1905
|
+
slip = (0, vehicleClearance_1.worstSlip)(s.fwd, s.w, radii);
|
|
1906
|
+
if (slip === null || slip < C.slipSettledFraction || Date.now() - tr > C.slipSettleMaxS * 1000)
|
|
1907
|
+
break;
|
|
1908
|
+
// no brake here: a dab of the foot brake locks a wheel (measured on the F40: slip 1.0 and 6.0 -> 2.4 m/s)
|
|
1909
|
+
await page.waitForTimeout(30);
|
|
1910
|
+
}
|
|
1911
|
+
trace.push({ phase: 'slip-settled', ...short(await state()), slip: slip === null ? null : +slip.toFixed(3), sinceReleaseS: +((Date.now() - tr) / 1000).toFixed(2) });
|
|
1912
|
+
}
|
|
1913
|
+
// coast: no drive torque, so a rolling wheel turns at exactly v / r. Guarded: the page ends the window early, and the car
|
|
1914
|
+
// is braked at once, if going on would take it past the margin before what is ahead. The chase frame (pinned to the body:
|
|
1915
|
+
// behind, left and above) is taken DURING the coast: the grounds have no room for the second it costs before or after.
|
|
1916
|
+
const limitM = Number.isFinite(room1.ahead.distanceM) ? from + room1.ahead.distanceM - C.marginM : Infinity;
|
|
1917
|
+
await frame.evaluate((c) => globalThis.__vqaVeh.setCam(c), { pos: [2.4, 1.5, -5.2], target: [0, 0.1, 0.6], fov: 40, near: 0.05 });
|
|
1918
|
+
const sampling = frame.evaluate(({ ms, guard }) => globalThis.__vqaVeh.sampleSpin(ms, guard), {
|
|
1919
|
+
ms: Math.round(C.coastS * 1000),
|
|
1920
|
+
guard: Number.isFinite(limitM) ? { origin: p0.p, dir: heading, limitM, decel: C.brakeDecelMps2 } : null,
|
|
1921
|
+
});
|
|
1922
|
+
await page.waitForTimeout(120);
|
|
1923
|
+
await shot('motion.driving', 'Rolling forward at speed (chase view, pinned to the body)', 'motion', 0);
|
|
1924
|
+
const guarded = await sampling;
|
|
1925
|
+
await clearCam();
|
|
1926
|
+
const raw = Array.isArray(guarded) ? guarded : guarded.samples;
|
|
1927
|
+
const stoppedForRoom = !Array.isArray(guarded) && guarded.stoppedForRoom;
|
|
1928
|
+
if (stoppedForRoom)
|
|
1929
|
+
await brakeTo0();
|
|
1930
|
+
const { samples, impactAtS } = (0, vehicleClearance_1.trimAtImpact)(raw);
|
|
1931
|
+
trace.push({ phase: 'coasted', ...short(await state()), ...(impactAtS !== null ? { impactAtS } : {}), ...(stoppedForRoom ? { stoppedForRoom: true } : {}) });
|
|
1932
|
+
await brakeTo0();
|
|
1933
|
+
trace.push({ phase: 'braked', ...short(await state()) });
|
|
1934
|
+
// 3. back to the start, closed loop on position along the start heading, bounded
|
|
1935
|
+
const back = {};
|
|
1936
|
+
if (process.env.HELIX_VQA_DEBUG_PARK_AT_WALL) {
|
|
1937
|
+
// Debug only (the caller records a harness error): park nose-on against what is ahead instead, the F40 round-1 end
|
|
1938
|
+
// state, to prove the exterior cameras stay clear of scenery on their own.
|
|
1939
|
+
const tb = Date.now();
|
|
1940
|
+
for (;;) {
|
|
1941
|
+
const r = await room();
|
|
1942
|
+
if (!(r.ahead.distanceM > 0.35) || Date.now() - tb > 12_000)
|
|
1943
|
+
break;
|
|
1944
|
+
await page.keyboard.down('KeyW');
|
|
1945
|
+
await page.waitForTimeout(r.ahead.distanceM > 2 ? 160 : 60);
|
|
1946
|
+
await page.keyboard.up('KeyW');
|
|
1947
|
+
await page.waitForTimeout(120);
|
|
1948
|
+
}
|
|
1949
|
+
await brakeTo0();
|
|
1950
|
+
const s = await state();
|
|
1951
|
+
const r = await room();
|
|
1952
|
+
Object.assign(back, { outcome: 'debug: parked against the scenery ahead (HELIX_VQA_DEBUG_PARK_AT_WALL)', endAlongM: +along(s.p).toFixed(2), clearAheadM: fin(r.ahead.distanceM), clearAheadBox: r.ahead.box });
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
let held = [];
|
|
1956
|
+
const hold = async (ks) => {
|
|
1957
|
+
for (const k of held)
|
|
1958
|
+
if (!ks.includes(k))
|
|
1959
|
+
await page.keyboard.up(k);
|
|
1960
|
+
for (const k of ks)
|
|
1961
|
+
if (!held.includes(k))
|
|
1962
|
+
await page.keyboard.down(k);
|
|
1963
|
+
held = ks;
|
|
1964
|
+
};
|
|
1965
|
+
const tb = Date.now();
|
|
1966
|
+
let last = 'coast';
|
|
1967
|
+
let steps = 0;
|
|
1968
|
+
try {
|
|
1969
|
+
for (;;) {
|
|
1970
|
+
const s = await state();
|
|
1971
|
+
last = (0, vehicleClearance_1.returnStep)({ alongM: along(s.p), speedMps: s.fwd });
|
|
1972
|
+
if (last === 'done' || Date.now() - tb > C.returnMaxS * 1000)
|
|
1973
|
+
break;
|
|
1974
|
+
await hold(last === 'forward' ? ['KeyW'] : last === 'reverse' ? ['KeyS'] : last === 'brake' ? [s.fwd > 0 ? 'KeyS' : 'KeyW', 'Space'] : []);
|
|
1975
|
+
steps++;
|
|
1976
|
+
await page.waitForTimeout(60);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
finally {
|
|
1980
|
+
await hold([]);
|
|
1981
|
+
}
|
|
1982
|
+
await brakeTo0();
|
|
1983
|
+
const s = await state();
|
|
1984
|
+
Object.assign(back, { outcome: last === 'done' ? 'returned' : 'timed out', seconds: +((Date.now() - tb) / 1000).toFixed(1), steps, endAlongM: +along(s.p).toFixed(2), endAcrossM: +across(s.p).toFixed(2), endFromStartM: +Math.hypot(s.p[0] - p0.p[0], s.p[1] - p0.p[1]).toFixed(2) });
|
|
1985
|
+
const room2 = await room();
|
|
1986
|
+
Object.assign(back, { clearAheadM: fin(room2.ahead.distanceM), clearAheadBox: room2.ahead.box, clearBehindM: fin(room2.behind.distanceM), clearBehindBox: room2.behind.box });
|
|
1987
|
+
trace.push({ phase: 'returned', ...short(s) });
|
|
1988
|
+
}
|
|
1989
|
+
const spin = (0, vehicleMath_1.spinAnalysis)(samples, rows.map((r) => r.id), radii);
|
|
1990
|
+
return {
|
|
1991
|
+
alive: aliveFacts,
|
|
1992
|
+
spin,
|
|
1993
|
+
mode: `keyboard (W/S/Space as the seated driver); sampled while coasting${impactAtS !== null ? ` (the first ${impactAtS} s, before the car met something)` : ''}${stoppedForRoom ? ' (ended early: the test grounds had no more room ahead; a harness limit, not the car)' : ''}`,
|
|
1994
|
+
facts: { start: short(p0), trace, peakForwardSpeed: +peak.toFixed(2), samples: samples.length, rawSamples: raw.length, impactAtS, stoppedForRoom, radii: rows.map((r, k) => ({ id: r.id, axle: r.axle ?? null, roll: radii[k], contact: r.physicsRadius, drawn: r.radius })), end: back },
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
finally {
|
|
1998
|
+
page.off('pageerror', onPageError);
|
|
1999
|
+
page.off('console', onConsole);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
//# sourceMappingURL=vehicle.js.map
|