@cimplify/sdk 0.108.0 → 0.110.2
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/LICENSE +21 -0
- package/dist/advanced.d.mts +4 -3
- package/dist/advanced.d.ts +4 -3
- package/dist/advanced.js +24 -24
- package/dist/advanced.mjs +5 -5
- package/dist/animation-4EXBXRVX.js +27 -0
- package/dist/animation-6DICNWUP.mjs +6 -0
- package/dist/audio-DhvyjqdA.d.mts +249 -0
- package/dist/audio-m0w8cnxq.d.ts +249 -0
- package/dist/avatars.d.mts +110 -0
- package/dist/avatars.d.ts +110 -0
- package/dist/avatars.js +82 -0
- package/dist/avatars.mjs +5 -0
- package/dist/backend-gUCzs5Kg.d.mts +3866 -0
- package/dist/backend-gUCzs5Kg.d.ts +3866 -0
- package/dist/{catalogue-B4eLL8Mi.d.ts → catalogue-CPTMyBUz.d.mts} +109 -221
- package/dist/catalogue-DzrTXP3v.d.mts +541 -0
- package/dist/catalogue-DzrTXP3v.d.ts +541 -0
- package/dist/{catalogue-OT89_Ifs.d.mts → catalogue-OTuS5zp-.d.ts} +109 -221
- package/dist/chunk-22BD3RHN.mjs +1809 -0
- package/dist/chunk-25T4JHEA.mjs +21 -0
- package/dist/chunk-2AQ237Y4.js +508 -0
- package/dist/chunk-2IC4H6OV.mjs +48 -0
- package/dist/chunk-2LHLG4OB.mjs +71 -0
- package/dist/chunk-2O2CG465.js +1709 -0
- package/dist/chunk-2Q4DTHXK.js +316 -0
- package/dist/chunk-2QTQ6BUH.mjs +46 -0
- package/dist/chunk-2UN4SFLR.mjs +100 -0
- package/dist/chunk-2V75I7K3.js +454 -0
- package/dist/chunk-2V7IL4HC.js +10 -0
- package/dist/chunk-34XBXUMW.js +296 -0
- package/dist/chunk-3C4C654I.js +71 -0
- package/dist/chunk-3DAD2LDF.mjs +72 -0
- package/dist/chunk-3GXH2NFD.mjs +4 -0
- package/dist/chunk-3O47ZFW3.js +961 -0
- package/dist/chunk-3OY3VYNF.mjs +11 -0
- package/dist/chunk-3S6WPBZL.js +2936 -0
- package/dist/chunk-3TUOUP5T.js +399 -0
- package/dist/chunk-3VEH23I5.mjs +1380 -0
- package/dist/chunk-3ZYSAW2T.mjs +263 -0
- package/dist/chunk-4ANVF274.js +103 -0
- package/dist/chunk-4C7I3ELN.js +388 -0
- package/dist/chunk-4DYKAYFO.mjs +8 -0
- package/dist/chunk-4ZDR72SG.js +329 -0
- package/dist/chunk-52YS45RR.js +1066 -0
- package/dist/chunk-5325XSCH.js +107 -0
- package/dist/chunk-5AD5X6AV.js +13 -0
- package/dist/chunk-5BYDGGWO.mjs +197 -0
- package/dist/chunk-5C3C7VDM.mjs +55 -0
- package/dist/chunk-5HFMY3D7.js +660 -0
- package/dist/chunk-5LH2QN3T.js +1862 -0
- package/dist/chunk-5PJEXSOW.js +44 -0
- package/dist/chunk-5SXDH3Y7.js +90 -0
- package/dist/chunk-5YFBGGCK.mjs +502 -0
- package/dist/chunk-5YWN6H5Q.mjs +133 -0
- package/dist/chunk-66QP3SMN.js +402 -0
- package/dist/chunk-6DAEZKIG.mjs +385 -0
- package/dist/chunk-6DATW7TB.mjs +420 -0
- package/dist/{chunk-JRGK3NXR.mjs → chunk-6ETDAU62.mjs} +72 -212
- package/dist/chunk-6H254EVP.mjs +178 -0
- package/dist/chunk-6PFNJPBJ.mjs +820 -0
- package/dist/chunk-6PJWXIFX.mjs +357 -0
- package/dist/chunk-6SWKWCMS.js +80 -0
- package/dist/chunk-6VVXXX7S.js +808 -0
- package/dist/chunk-6WUBSMTX.js +1403 -0
- package/dist/chunk-73SGZLAQ.mjs +35 -0
- package/dist/chunk-76L2V6KD.mjs +4 -0
- package/dist/chunk-7AAIAVAA.js +1531 -0
- package/dist/chunk-7ARSJPV3.mjs +93 -0
- package/dist/chunk-7BHW5GTC.mjs +1234 -0
- package/dist/chunk-7DQSDKDU.js +6 -0
- package/dist/chunk-7IQQQENP.mjs +401 -0
- package/dist/chunk-7KFTYENS.js +11357 -0
- package/dist/chunk-7SM6XSPZ.js +3171 -0
- package/dist/chunk-7SXOHAIR.mjs +28 -0
- package/dist/chunk-A2MAX6S4.js +90 -0
- package/dist/chunk-A33OORT7.mjs +1530 -0
- package/dist/{chunk-U6ACF76H.mjs → chunk-A36JRXXH.mjs} +1075 -1845
- package/dist/chunk-A7BU3BK4.js +52 -0
- package/dist/chunk-A7MQSN2V.mjs +77 -0
- package/dist/chunk-A7V4L2IT.mjs +416 -0
- package/dist/chunk-AABEK6XU.js +226 -0
- package/dist/chunk-AGV33FWC.mjs +5005 -0
- package/dist/chunk-AHNR3BXI.js +108 -0
- package/dist/chunk-AQ5RLBU2.mjs +286 -0
- package/dist/chunk-AQOE6WCG.js +1270 -0
- package/dist/chunk-ATAWRACV.mjs +62 -0
- package/dist/chunk-ATCURNUV.js +14 -0
- package/dist/chunk-AWPV33OO.js +6 -0
- package/dist/chunk-AYJPHKYV.js +337 -0
- package/dist/chunk-AZ5BGWHB.js +162 -0
- package/dist/chunk-AZAXD44P.js +1373 -0
- package/dist/chunk-BBGKQIJJ.mjs +406 -0
- package/dist/chunk-BP4JLSUA.js +1453 -0
- package/dist/{chunk-SUCE7FWA.mjs → chunk-BTUSVHIE.mjs} +539 -978
- package/dist/chunk-BU6OK542.mjs +818 -0
- package/dist/chunk-CVSRWM6W.js +418 -0
- package/dist/chunk-CYBMFMJD.mjs +247 -0
- package/dist/chunk-DDJLDOUX.mjs +217 -0
- package/dist/{chunk-V32BCG7M.js → chunk-DFZWHNRG.js} +75 -215
- package/dist/chunk-DJ2C7AVG.js +644 -0
- package/dist/chunk-DS2VU4WW.mjs +102 -0
- package/dist/chunk-DSAG7T5P.js +7290 -0
- package/dist/chunk-E2QPPH7I.js +327 -0
- package/dist/chunk-E65WCTIG.mjs +1187 -0
- package/dist/{chunk-33VCYWVL.mjs → chunk-E6QQKDNE.mjs} +30 -42
- package/dist/chunk-ECDK6QBF.mjs +981 -0
- package/dist/chunk-EIB4OWVQ.mjs +620 -0
- package/dist/chunk-EQUZYCGO.js +250 -0
- package/dist/chunk-F3UJ6ZRM.mjs +313 -0
- package/dist/chunk-F63N2ZTQ.js +7023 -0
- package/dist/chunk-F6XRYEM3.mjs +26 -0
- package/dist/chunk-FE6VCE4K.js +5030 -0
- package/dist/chunk-FLWD6GRR.js +1372 -0
- package/dist/chunk-FQBFWJBS.mjs +110 -0
- package/dist/chunk-FXVJ2HFU.js +26 -0
- package/dist/chunk-FZMQMQBW.js +312 -0
- package/dist/chunk-G3EEC44R.js +53 -0
- package/dist/{chunk-KG3WT4PR.mjs → chunk-GCKWKLZF.mjs} +73 -104
- package/dist/chunk-GCX7TY4D.mjs +6826 -0
- package/dist/chunk-GDB6TCSD.js +450 -0
- package/dist/chunk-GDCV4IWO.mjs +21 -0
- package/dist/chunk-GDZT5LZ5.js +170 -0
- package/dist/chunk-GHPUYD4I.mjs +213 -0
- package/dist/chunk-GHS6MIL6.mjs +1432 -0
- package/dist/chunk-GPS2THCL.mjs +11 -0
- package/dist/{chunk-EV2EVOYS.js → chunk-GTJWQAGU.js} +31 -42
- package/dist/chunk-GVTI6UJQ.mjs +130 -0
- package/dist/chunk-GXKI64OT.mjs +1138 -0
- package/dist/chunk-GYDYF2WX.js +275 -0
- package/dist/chunk-GYLP6UG4.mjs +705 -0
- package/dist/chunk-H3OQKK6X.js +1158 -0
- package/dist/chunk-H5DFSCJ6.js +40 -0
- package/dist/chunk-HAGEIC2L.mjs +186 -0
- package/dist/chunk-HAJ4JRFO.js +293 -0
- package/dist/chunk-HDR72GB6.js +188 -0
- package/dist/chunk-HF5HPS4T.mjs +40 -0
- package/dist/chunk-HK6LOFDS.js +218 -0
- package/dist/chunk-HMWYTXCY.mjs +143 -0
- package/dist/chunk-HPCSRCMA.js +89 -0
- package/dist/chunk-HSEO6TPU.js +247 -0
- package/dist/chunk-I5FOHCLH.js +132 -0
- package/dist/chunk-IDLW4NWA.mjs +85 -0
- package/dist/chunk-IIDZ5WUX.js +61 -0
- package/dist/chunk-IR74FJHU.js +2332 -0
- package/dist/chunk-IRVFYAKP.mjs +100 -0
- package/dist/{chunk-XBL3TMPC.mjs → chunk-IZDWAL53.mjs} +1795 -3287
- package/dist/chunk-J2KZXIU3.js +51 -0
- package/dist/chunk-J2U6PJSO.js +91 -0
- package/dist/chunk-J5AJNY2N.js +283 -0
- package/dist/chunk-J6KQZ4FO.mjs +821 -0
- package/dist/{chunk-OWW5GUSB.js → chunk-JGBCIKJV.js} +7 -6
- package/dist/chunk-JKGTBMU7.mjs +25 -0
- package/dist/chunk-JR3TNEQC.js +422 -0
- package/dist/chunk-JURRXY2J.mjs +29 -0
- package/dist/chunk-K3HW5I2A.mjs +317 -0
- package/dist/chunk-K46SMZ66.mjs +103 -0
- package/dist/chunk-K4UDGE3Z.mjs +6 -0
- package/dist/chunk-KDQOR4QQ.mjs +350 -0
- package/dist/chunk-KDTVOLHI.mjs +288 -0
- package/dist/chunk-KGHJH5KJ.mjs +35 -0
- package/dist/chunk-KIZOA5LX.mjs +140 -0
- package/dist/chunk-KK6HYVBI.mjs +242 -0
- package/dist/chunk-KMKWGXMK.js +856 -0
- package/dist/chunk-KPA6K5IB.js +80 -0
- package/dist/chunk-KU7O4IXU.mjs +260 -0
- package/dist/chunk-KVFNRJOR.mjs +11142 -0
- package/dist/chunk-KZJRSXHK.js +2432 -0
- package/dist/chunk-LHWPRUJL.js +79 -0
- package/dist/chunk-LIV3KXJG.mjs +369 -0
- package/dist/chunk-LKV4LACG.js +21 -0
- package/dist/chunk-LUY45X3U.mjs +263 -0
- package/dist/chunk-LYEZZEEF.mjs +17 -0
- package/dist/chunk-M5EXQZYY.mjs +1519 -0
- package/dist/chunk-ME6HGG4I.mjs +1338 -0
- package/dist/chunk-MEXP6DA6.js +31 -0
- package/dist/chunk-MG52AW3H.js +902 -0
- package/dist/chunk-MIPO6F72.js +334 -0
- package/dist/{chunk-5QW7CQBA.js → chunk-MKIJQL5Q.js} +211 -321
- package/dist/chunk-MN3YETVN.mjs +17 -0
- package/dist/chunk-MOBSBXVE.mjs +2391 -0
- package/dist/chunk-MOMOZEUM.mjs +88 -0
- package/dist/chunk-MRFUNRAP.js +62 -0
- package/dist/chunk-MTIHEW2M.js +433 -0
- package/dist/chunk-N4RGGDEL.mjs +73 -0
- package/dist/chunk-NH654HAT.js +10 -0
- package/dist/chunk-NHJUGZUR.mjs +253 -0
- package/dist/chunk-NMQT4HG3.mjs +757 -0
- package/dist/chunk-NPSYB6DC.mjs +906 -0
- package/dist/chunk-NW2GPMSO.js +857 -0
- package/dist/chunk-NXKQOQ6L.js +76 -0
- package/dist/chunk-NXLAZIJZ.js +309 -0
- package/dist/chunk-NYWNA5CF.js +148 -0
- package/dist/chunk-NZTKLY2W.js +1442 -0
- package/dist/chunk-O2ICABUD.js +36 -0
- package/dist/chunk-OIVVQQ22.js +862 -0
- package/dist/chunk-OKC2B4O3.mjs +312 -0
- package/dist/chunk-OLWFPDBK.js +2220 -0
- package/dist/chunk-OMTVH334.js +3779 -0
- package/dist/chunk-OQRVEM56.mjs +362 -0
- package/dist/chunk-OXMIBOD4.js +337 -0
- package/dist/chunk-PBL6YL5L.mjs +288 -0
- package/dist/chunk-PDOELNNQ.js +97 -0
- package/dist/chunk-PDQJY2GZ.js +136 -0
- package/dist/chunk-PKOZXD2Q.js +1274 -0
- package/dist/chunk-PPF2BYFL.mjs +2191 -0
- package/dist/chunk-PQTQKNKD.js +539 -0
- package/dist/chunk-PRSR4FAV.mjs +81 -0
- package/dist/chunk-PTRNO5UD.js +95 -0
- package/dist/chunk-Q2Y3VMKH.js +2000 -0
- package/dist/chunk-QCZOE65N.js +96 -0
- package/dist/chunk-QDYFLATW.mjs +52 -0
- package/dist/chunk-QQABGCMM.js +22 -0
- package/dist/chunk-QS6TRHMR.js +345 -0
- package/dist/chunk-R56P64LB.mjs +208 -0
- package/dist/chunk-RCQ2MPBV.js +428 -0
- package/dist/chunk-RFVRPYR2.mjs +856 -0
- package/dist/chunk-RKBLPGPV.js +11521 -0
- package/dist/chunk-RM2V4IND.mjs +327 -0
- package/dist/chunk-RQXN5A34.mjs +613 -0
- package/dist/chunk-RUVGVNOF.mjs +2687 -0
- package/dist/chunk-SAFJIGYB.mjs +165 -0
- package/dist/chunk-SKQFLBDD.js +263 -0
- package/dist/chunk-SMK6JU2F.mjs +2092 -0
- package/dist/{chunk-UVRPC2P7.js → chunk-SNNEOS5Z.js} +1984 -3476
- package/dist/chunk-SPCRQ6NO.mjs +1246 -0
- package/dist/chunk-SWX4SALA.mjs +19 -0
- package/dist/{chunk-JVODCGFP.mjs → chunk-SYEO2WV4.mjs} +128 -238
- package/dist/chunk-T2QQMX2N.js +10 -0
- package/dist/chunk-T3AFF3L7.mjs +57 -0
- package/dist/chunk-T6Z2EXYL.js +712 -0
- package/dist/chunk-T7LOXGYX.js +118 -0
- package/dist/chunk-TJCAE7RC.mjs +1315 -0
- package/dist/chunk-TJI3K6RP.js +137 -0
- package/dist/chunk-TL2SVO4P.mjs +55 -0
- package/dist/chunk-TM63CYGN.mjs +399 -0
- package/dist/chunk-TU5RYTYU.mjs +13 -0
- package/dist/chunk-TVJZ4GPS.js +1553 -0
- package/dist/chunk-TWM6DLYF.js +371 -0
- package/dist/chunk-U63OC4TQ.js +30 -0
- package/dist/{chunk-L5GKVQVJ.js → chunk-U6BVSJZ6.js} +25 -57
- package/dist/chunk-U6UVWEAK.mjs +792 -0
- package/dist/chunk-UCEPNO6N.mjs +11048 -0
- package/dist/{chunk-ANT574FD.js → chunk-UCR5HRYM.js} +74 -105
- package/dist/chunk-UG5BTOTF.mjs +334 -0
- package/dist/chunk-UGH37BQW.mjs +157 -0
- package/dist/chunk-V7ZRH3PY.mjs +271 -0
- package/dist/chunk-VBGHBL3E.mjs +1394 -0
- package/dist/chunk-VRBDAS5I.js +920 -0
- package/dist/chunk-VVE3Y76O.js +230 -0
- package/dist/chunk-W65JIRI4.mjs +1182 -0
- package/dist/chunk-WHYKAT7G.js +62 -0
- package/dist/{chunk-YOIVZ6OA.mjs → chunk-WIZRUIZB.mjs} +25 -57
- package/dist/chunk-WKWTDZS7.js +385 -0
- package/dist/chunk-WLMINRC4.mjs +78 -0
- package/dist/chunk-WOF3AD3J.js +22 -0
- package/dist/chunk-WWKMOA76.js +1262 -0
- package/dist/chunk-X5A5FOWW.js +615 -0
- package/dist/chunk-X6CN6KQF.mjs +192 -0
- package/dist/chunk-XD2PMH46.mjs +517 -0
- package/dist/chunk-XJA2NGTI.mjs +88 -0
- package/dist/chunk-XJIHN3BY.mjs +8 -0
- package/dist/chunk-XK6UDWJS.js +203 -0
- package/dist/chunk-XPG3N5CF.mjs +415 -0
- package/dist/chunk-XVZ2NKSU.mjs +74 -0
- package/dist/chunk-XYP45OLE.js +117 -0
- package/dist/chunk-XZSDGZX6.mjs +301 -0
- package/dist/chunk-Y2EMGN4T.mjs +120 -0
- package/dist/chunk-Y3XWP5XW.mjs +298 -0
- package/dist/chunk-Y4K22JL6.js +228 -0
- package/dist/chunk-Y65LXQGA.mjs +3164 -0
- package/dist/chunk-Y66WVMO5.mjs +87 -0
- package/dist/chunk-Y7JWLB25.mjs +7280 -0
- package/dist/chunk-YCDXAJKU.js +175 -0
- package/dist/chunk-YRDQCCIS.js +152 -0
- package/dist/chunk-YV52DGSA.js +111 -0
- package/dist/chunk-YXGZQPUD.js +94 -0
- package/dist/chunk-Z3A3AFVU.mjs +102 -0
- package/dist/chunk-ZCY6YKKP.mjs +475 -0
- package/dist/chunk-ZEQHXVU5.js +215 -0
- package/dist/chunk-ZF7JCXCI.js +284 -0
- package/dist/chunk-ZJBRUH6V.js +29 -0
- package/dist/chunk-ZMIL4ZOA.js +365 -0
- package/dist/chunk-ZR5ULMN2.mjs +337 -0
- package/dist/chunk-ZXREOYOW.js +87 -0
- package/dist/{client-BSP9U1O1.d.mts → client-CX_WR0-J.d.mts} +2 -2
- package/dist/client-Dytx_ZTC.d.ts +230 -0
- package/dist/{client-B878xeAP.d.ts → client-dqOhP9Nc.d.ts} +2 -2
- package/dist/client-hTKbCuhG.d.mts +230 -0
- package/dist/clock-B-UAjkKa.d.mts +5 -0
- package/dist/clock-B-UAjkKa.d.ts +5 -0
- package/dist/config-BlXQluJR.d.mts +36 -0
- package/dist/config-BlXQluJR.d.ts +36 -0
- package/dist/courier-Cnn-edJ4.d.ts +95 -0
- package/dist/courier-FmGfkvIN.d.mts +95 -0
- package/dist/crowd-DdHFbqKK.d.ts +186 -0
- package/dist/crowd-hOVSAmA3.d.mts +186 -0
- package/dist/danfo-BEBapFlX.d.mts +146 -0
- package/dist/danfo-UQ_PTIIx.d.ts +146 -0
- package/dist/{elements-Bty6Qs_N.d.mts → elements-DdsRig-2.d.mts} +1548 -257
- package/dist/{elements-Bty6Qs_N.d.ts → elements-DdsRig-2.d.ts} +1548 -257
- package/dist/engines/godot.d.mts +56 -0
- package/dist/engines/godot.d.ts +56 -0
- package/dist/engines/godot.js +28 -0
- package/dist/engines/godot.mjs +3 -0
- package/dist/engines/wasm.d.mts +11 -0
- package/dist/engines/wasm.d.ts +11 -0
- package/dist/engines/wasm.js +178 -0
- package/dist/engines/wasm.mjs +176 -0
- package/dist/fallback-CrG_lTFk.d.mts +270 -0
- package/dist/fallback-CrG_lTFk.d.ts +270 -0
- package/dist/fallback-KDB4Y2CE.mjs +6 -0
- package/dist/fallback-Y26TQENE.js +19 -0
- package/dist/features-CZB4rmcD.d.mts +118 -0
- package/dist/features-CZB4rmcD.d.ts +118 -0
- package/dist/fit-BWiE7rgA.d.mts +44 -0
- package/dist/fit-DK0oT7zO.d.ts +44 -0
- package/dist/five-a-side-Bv1AOUy3.d.mts +115 -0
- package/dist/five-a-side-EC51SJY1.d.ts +115 -0
- package/dist/fluid-ARr0qUIL.d.ts +939 -0
- package/dist/fluid-DLlEj2cs.d.mts +939 -0
- package/dist/game/conformance.d.mts +84 -0
- package/dist/game/conformance.d.ts +84 -0
- package/dist/game/conformance.js +1025 -0
- package/dist/game/conformance.mjs +994 -0
- package/dist/game/heap.d.mts +40 -0
- package/dist/game/heap.d.ts +40 -0
- package/dist/game/heap.js +48 -0
- package/dist/game/heap.mjs +7 -0
- package/dist/game/kit/canvas.d.mts +240 -0
- package/dist/game/kit/canvas.d.ts +240 -0
- package/dist/game/kit/canvas.js +3566 -0
- package/dist/game/kit/canvas.mjs +1039 -0
- package/dist/game/kit/courier.d.mts +17 -0
- package/dist/game/kit/courier.d.ts +17 -0
- package/dist/game/kit/courier.js +66 -0
- package/dist/game/kit/courier.mjs +49 -0
- package/dist/game/kit/danfo.d.mts +11 -0
- package/dist/game/kit/danfo.d.ts +11 -0
- package/dist/game/kit/danfo.js +109 -0
- package/dist/game/kit/danfo.mjs +44 -0
- package/dist/game/kit/engine.d.mts +29 -0
- package/dist/game/kit/engine.d.ts +29 -0
- package/dist/game/kit/engine.js +183 -0
- package/dist/game/kit/engine.mjs +30 -0
- package/dist/game/kit/five-a-side.d.mts +11 -0
- package/dist/game/kit/five-a-side.d.ts +11 -0
- package/dist/game/kit/five-a-side.js +86 -0
- package/dist/game/kit/five-a-side.mjs +41 -0
- package/dist/game/kit/frontier.d.mts +344 -0
- package/dist/game/kit/frontier.d.ts +344 -0
- package/dist/game/kit/frontier.js +435 -0
- package/dist/game/kit/frontier.mjs +14 -0
- package/dist/game/kit/genesis.d.mts +752 -0
- package/dist/game/kit/genesis.d.ts +752 -0
- package/dist/game/kit/genesis.js +380 -0
- package/dist/game/kit/genesis.mjs +15 -0
- package/dist/game/kit/ludo.d.mts +15 -0
- package/dist/game/kit/ludo.d.ts +15 -0
- package/dist/game/kit/ludo.js +87 -0
- package/dist/game/kit/ludo.mjs +42 -0
- package/dist/game/kit/runner.d.mts +12 -0
- package/dist/game/kit/runner.d.ts +12 -0
- package/dist/game/kit/runner.js +83 -0
- package/dist/game/kit/runner.mjs +46 -0
- package/dist/game/kit/shootout.d.mts +13 -0
- package/dist/game/kit/shootout.d.ts +13 -0
- package/dist/game/kit/shootout.js +122 -0
- package/dist/game/kit/shootout.mjs +57 -0
- package/dist/game/kit/slots.d.mts +392 -0
- package/dist/game/kit/slots.d.ts +392 -0
- package/dist/game/kit/slots.js +215 -0
- package/dist/game/kit/slots.mjs +6 -0
- package/dist/game/kit/tycoon.d.mts +16 -0
- package/dist/game/kit/tycoon.d.ts +16 -0
- package/dist/game/kit/tycoon.js +145 -0
- package/dist/game/kit/tycoon.mjs +44 -0
- package/dist/game/kit/word.d.mts +11 -0
- package/dist/game/kit/word.d.ts +11 -0
- package/dist/game/kit/word.js +78 -0
- package/dist/game/kit/word.mjs +41 -0
- package/dist/game/kit/worlds.d.mts +18 -0
- package/dist/game/kit/worlds.d.ts +18 -0
- package/dist/game/kit/worlds.js +74 -0
- package/dist/game/kit/worlds.mjs +49 -0
- package/dist/game/kit.d.mts +53 -0
- package/dist/game/kit.d.ts +53 -0
- package/dist/game/kit.js +2610 -0
- package/dist/game/kit.mjs +101 -0
- package/dist/game/prepared.d.mts +14 -0
- package/dist/game/prepared.d.ts +14 -0
- package/dist/game/prepared.js +81 -0
- package/dist/game/prepared.mjs +32 -0
- package/dist/game-Bx8onkhX.d.mts +1451 -0
- package/dist/game-fgQy2YIm.d.ts +1451 -0
- package/dist/game-theme-CBTw1b_d.d.mts +35 -0
- package/dist/game-theme-Dg-ict5A.d.ts +35 -0
- package/dist/game.d.mts +3546 -0
- package/dist/game.d.ts +3546 -0
- package/dist/game.js +4455 -0
- package/dist/game.mjs +50 -0
- package/dist/graphics-C6YjWYvP.d.ts +5 -0
- package/dist/graphics-Dk2yQnYN.d.mts +5 -0
- package/dist/guest-DrsA00XD.d.mts +732 -0
- package/dist/guest-wIdfoT05.d.ts +732 -0
- package/dist/hit-stop-BKkVwTq7.d.mts +43 -0
- package/dist/hit-stop-D-8pPYnM.d.ts +43 -0
- package/dist/hot-Crn9XpHJ.d.ts +29 -0
- package/dist/hot-DH3nOhQD.d.mts +29 -0
- package/dist/{index-CQStGb3B.d.mts → index-6gkHMGAq.d.mts} +2 -2
- package/dist/index-B8ybCKJI.d.ts +41 -0
- package/dist/{index-DJwXmoaP.d.ts → index-B98Xac8e.d.ts} +2 -2
- package/dist/{index-B7oA_1XI.d.ts → index-B_ByTtIT.d.ts} +2 -2
- package/dist/index-DHjNHb1c.d.mts +41 -0
- package/dist/{index-DhUSTAhT.d.mts → index-DbAN_VQE.d.mts} +2 -2
- package/dist/index.d.mts +134 -24
- package/dist/index.d.ts +134 -24
- package/dist/index.js +407 -248
- package/dist/index.mjs +184 -58
- package/dist/interactives-authoring.d.mts +231 -0
- package/dist/interactives-authoring.d.ts +231 -0
- package/dist/interactives-authoring.js +1743 -0
- package/dist/interactives-authoring.mjs +1704 -0
- package/dist/interactives-canvas.global.js +148 -0
- package/dist/interactives-guest.d.mts +20 -0
- package/dist/interactives-guest.d.ts +20 -0
- package/dist/interactives-guest.js +63 -0
- package/dist/interactives-guest.json +1 -0
- package/dist/interactives-guest.mjs +61 -0
- package/dist/interactives-jsc.global.js +9357 -0
- package/dist/interactives-meters.d.mts +93 -0
- package/dist/interactives-meters.d.ts +93 -0
- package/dist/interactives-meters.js +252 -0
- package/dist/interactives-meters.mjs +68 -0
- package/dist/interactives-production.d.mts +430 -0
- package/dist/interactives-production.d.ts +430 -0
- package/dist/interactives-production.js +1231 -0
- package/dist/interactives-production.mjs +1170 -0
- package/dist/interactives-record.d.mts +112 -0
- package/dist/interactives-record.d.ts +112 -0
- package/dist/interactives-record.js +302 -0
- package/dist/interactives-record.mjs +295 -0
- package/dist/interactives-runtime.d.mts +61 -0
- package/dist/interactives-runtime.d.ts +61 -0
- package/dist/interactives-runtime.js +442 -0
- package/dist/interactives-runtime.mjs +25 -0
- package/dist/interactives-studio-kernel.global.js +1 -0
- package/dist/interactives-studio.d.mts +806 -0
- package/dist/interactives-studio.d.ts +806 -0
- package/dist/interactives-studio.js +5741 -0
- package/dist/interactives-studio.mjs +5651 -0
- package/dist/interactives-tick.d.mts +90 -0
- package/dist/interactives-tick.d.ts +90 -0
- package/dist/interactives-tick.js +203 -0
- package/dist/interactives-tick.mjs +6 -0
- package/dist/interactives-web.d.mts +762 -0
- package/dist/interactives-web.d.ts +762 -0
- package/dist/interactives-web.js +5000 -0
- package/dist/interactives-web.mjs +4628 -0
- package/dist/interactives.d.mts +2430 -0
- package/dist/interactives.d.ts +2430 -0
- package/dist/interactives.js +5723 -0
- package/dist/interactives.mjs +4333 -0
- package/dist/juice-CoHxlpKA.d.ts +42 -0
- package/dist/juice-DQztFS43.d.mts +42 -0
- package/dist/launch-T2f4_Di4.d.mts +151 -0
- package/dist/launch-d6oreMzR.d.ts +151 -0
- package/dist/library-DXYaMFhi.d.mts +69 -0
- package/dist/library-DXYaMFhi.d.ts +69 -0
- package/dist/link-BUTqGmyi.d.mts +38 -0
- package/dist/link-BUTqGmyi.d.ts +38 -0
- package/dist/lint.js +104 -204
- package/dist/lint.mjs +103 -203
- package/dist/local-D8_I6UeL.d.mts +6967 -0
- package/dist/local-VZkqsCzR.d.ts +6967 -0
- package/dist/lockstep-BLf6dglI.d.mts +321 -0
- package/dist/lockstep-D50Ia0JW.d.ts +321 -0
- package/dist/ludo-B8ClkDqr.d.ts +205 -0
- package/dist/ludo-BOuohQ-M.d.mts +205 -0
- package/dist/mock/cli.mjs +200 -56
- package/dist/mock/library.js +200 -56
- package/dist/mock/library.mjs +200 -56
- package/dist/mock/msw.js +200 -56
- package/dist/mock/msw.mjs +200 -56
- package/dist/native-BodxSh03.d.mts +16 -0
- package/dist/native-DUHMT8gA.d.ts +16 -0
- package/dist/network-ZXLD7l1b.d.mts +19 -0
- package/dist/network-ZXLD7l1b.d.ts +19 -0
- package/dist/online-XeymeM9s.d.ts +381 -0
- package/dist/online-d7W_Al9a.d.mts +381 -0
- package/dist/params-DxFb5G_U.d.mts +155 -0
- package/dist/params-DxFb5G_U.d.ts +155 -0
- package/dist/play-BRRlfLhX.d.ts +940 -0
- package/dist/play-ClrMd_e8.d.mts +940 -0
- package/dist/policy-CCw8tXLm.d.mts +3078 -0
- package/dist/policy-CMqOSQIu.d.ts +3078 -0
- package/dist/ports-B_0v_P3A.d.mts +13 -0
- package/dist/ports-CGU_3e_0.d.ts +13 -0
- package/dist/prefetch-BHnicOQj.d.ts +117 -0
- package/dist/prefetch-CBXgYacv.d.mts +117 -0
- package/dist/{price-CSztR02F.d.mts → price-BOzexBww.d.mts} +1 -1
- package/dist/{price-C_vasx9A.d.ts → price-C1h9sPxE.d.ts} +1 -1
- package/dist/protocol-C8qbpUbT.d.mts +242 -0
- package/dist/protocol-C8qbpUbT.d.ts +242 -0
- package/dist/rapier-AZHT4YI7.js +7886 -0
- package/dist/rapier-ODEPCQA6.mjs +7792 -0
- package/dist/react.d.mts +157 -15
- package/dist/react.d.ts +157 -15
- package/dist/react.js +5564 -5904
- package/dist/react.mjs +5451 -5808
- package/dist/runner-CQcLL7By.d.ts +189 -0
- package/dist/runner-D46Yj-wE.d.mts +189 -0
- package/dist/runtime-BRWfkf_q.d.mts +51 -0
- package/dist/runtime-DyuBMkKM.d.ts +51 -0
- package/dist/scheduling-D2ajXpMC.d.ts +224 -0
- package/dist/scheduling-DSRbbaZb.d.mts +224 -0
- package/dist/schema-BrWxzKp6.d.mts +63 -0
- package/dist/schema-BrWxzKp6.d.ts +63 -0
- package/dist/server/evict.js +34 -37
- package/dist/server/evict.mjs +34 -37
- package/dist/server/tanstack.d.mts +49 -3
- package/dist/server/tanstack.d.ts +49 -3
- package/dist/server/tanstack.js +169 -117
- package/dist/server/tanstack.mjs +166 -118
- package/dist/{server-Dckg4Vdr.d.ts → server-D29IIuDV.d.ts} +136 -130
- package/dist/{server-B6dW7-Lj.d.mts → server-DQMWPnc8.d.mts} +136 -130
- package/dist/server.d.mts +45 -5
- package/dist/server.d.ts +45 -5
- package/dist/server.js +171 -227
- package/dist/server.mjs +165 -225
- package/dist/session-grant-CJ27DwrT.d.mts +19 -0
- package/dist/session-grant-CJ27DwrT.d.ts +19 -0
- package/dist/shootout-BJZeX6FG.d.ts +140 -0
- package/dist/shootout-tcZd1Rxt.d.mts +140 -0
- package/dist/state-replica-stMtdkVD.d.mts +42 -0
- package/dist/state-replica-stMtdkVD.d.ts +42 -0
- package/dist/studio-CowqWqez.d.mts +14 -0
- package/dist/studio-CowqWqez.d.ts +14 -0
- package/dist/styles.css +1 -1
- package/dist/template-AhCdB1kW.d.mts +190 -0
- package/dist/template-BkEGVTQj.d.ts +190 -0
- package/dist/terrain-BMxEc6Oc.d.ts +429 -0
- package/dist/terrain-C-XC1Oui.d.mts +429 -0
- package/dist/testing/msw.d.mts +28 -4
- package/dist/testing/msw.d.ts +28 -4
- package/dist/testing/msw.js +15 -21
- package/dist/testing/msw.mjs +15 -21
- package/dist/testing/suite.d.mts +6 -5
- package/dist/testing/suite.d.ts +6 -5
- package/dist/testing/suite.js +89 -124
- package/dist/testing/suite.mjs +81 -116
- package/dist/testing.d.mts +27 -1227
- package/dist/testing.d.ts +27 -1227
- package/dist/testing.js +127 -131
- package/dist/testing.mjs +19 -23
- package/dist/tick-transport-C8QEh8Uw.d.mts +50 -0
- package/dist/tick-transport-UjHY3xK1.d.ts +50 -0
- package/dist/timeline-protocol-hp6e8zFB.d.mts +47 -0
- package/dist/timeline-protocol-hp6e8zFB.d.ts +47 -0
- package/dist/tokens-DhanQRQS.d.mts +21 -0
- package/dist/tokens-K8yxwd6G.d.ts +21 -0
- package/dist/tree-C-Io2Hbx.d.mts +35 -0
- package/dist/tree-CP_qSW4H.d.ts +35 -0
- package/dist/tycoon-B5KHk2Xx.d.mts +255 -0
- package/dist/tycoon-hmHo-3HX.d.ts +255 -0
- package/dist/types-BQkxPwZP.d.mts +219 -0
- package/dist/types-D_f6UUxM.d.ts +219 -0
- package/dist/ui-DwHicuFv.d.mts +563 -0
- package/dist/ui-hi7S2YcH.d.ts +563 -0
- package/dist/{sign-in-DQlIdqsi.d.mts → url-52ndUruG.d.mts} +15 -1
- package/dist/{sign-in-DQlIdqsi.d.ts → url-52ndUruG.d.ts} +15 -1
- package/dist/utils.d.mts +3 -3
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +37 -37
- package/dist/utils.mjs +4 -4
- package/dist/vars-Dc_PrhBK.d.mts +511 -0
- package/dist/vars-Dc_PrhBK.d.ts +511 -0
- package/dist/vm-IUARFFAI.mjs +63 -0
- package/dist/vm-N7L6MGGH.js +65 -0
- package/dist/weave-delivery-CW-Kstut.d.mts +360 -0
- package/dist/weave-delivery-CW-Kstut.d.ts +360 -0
- package/dist/web-Dss74a7v.d.ts +883 -0
- package/dist/web-f9ZQPoyw.d.mts +883 -0
- package/dist/web-transport-C7MkBL0a.d.ts +19 -0
- package/dist/web-transport-aYEqXMd9.d.mts +19 -0
- package/dist/weight-CPGjsgjV.d.ts +92 -0
- package/dist/weight-tF1ODR1p.d.mts +92 -0
- package/dist/word-BcX9Jcmv.d.mts +70 -0
- package/dist/word-C7sVPLHm.d.ts +70 -0
- package/dist/world-PvILHDR1.d.ts +2285 -0
- package/dist/world-qekkXjJv.d.mts +2285 -0
- package/dist/worlds-Br2Pb0Cs.d.ts +109 -0
- package/dist/worlds-D5ydYr7d.d.mts +109 -0
- package/engines/c/cimplify.h +108 -0
- package/engines/c/cimplify.lib.js +11 -0
- package/engines/godot/addons/cimplify/cimplify.gd +97 -0
- package/engines/rust/interactives-ctx/Cargo.toml +9 -0
- package/engines/rust/interactives-ctx/src/lib.rs +189 -0
- package/package.json +189 -3
- package/vendor/rapier3d-compat/LICENSE +201 -0
- package/vendor/rapier3d-compat/PATCHES.md +61 -0
- package/dist/chunk-2P4KXFB6.js +0 -274
- package/dist/chunk-3G6RQLXK.mjs +0 -21
- package/dist/chunk-3V4DVXV3.mjs +0 -254
- package/dist/chunk-EDK3LNM6.js +0 -2436
- package/dist/chunk-F7J3HK27.js +0 -4549
- package/dist/chunk-U4MW6PYU.mjs +0 -23
- package/dist/chunk-XREIZ56K.js +0 -26
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
import { collectYoung, measureAllocations, warmUp, measureSteadyAllocations, profileAllocationSites } from '../chunk-6H254EVP.mjs';
|
|
2
|
+
export { collectGarbage, createNodeHeapMeter, measureAllocations, profileAllocationSites, warmUp } from '../chunk-6H254EVP.mjs';
|
|
3
|
+
import { kitPublicationGates } from '../chunk-XZSDGZX6.mjs';
|
|
4
|
+
import '../chunk-3DAD2LDF.mjs';
|
|
5
|
+
import '../chunk-4DYKAYFO.mjs';
|
|
6
|
+
import { createTemplateHarness } from '../chunk-KGHJH5KJ.mjs';
|
|
7
|
+
import { checkGameSources } from '../chunk-R56P64LB.mjs';
|
|
8
|
+
import { sphereGeometry, boxGeometry } from '../chunk-GHPUYD4I.mjs';
|
|
9
|
+
import { createGame, World } from '../chunk-KVFNRJOR.mjs';
|
|
10
|
+
import { Ease, Repeat, TweenTarget } from '../chunk-KDTVOLHI.mjs';
|
|
11
|
+
import { quantized, LoopbackTransport, MessageWriter, SnapshotBuffer } from '../chunk-RM2V4IND.mjs';
|
|
12
|
+
import '../chunk-22BD3RHN.mjs';
|
|
13
|
+
import { defineComponent } from '../chunk-ME6HGG4I.mjs';
|
|
14
|
+
import '../chunk-5BYDGGWO.mjs';
|
|
15
|
+
import { emptyClipState } from '../chunk-7ARSJPV3.mjs';
|
|
16
|
+
import '../chunk-RUVGVNOF.mjs';
|
|
17
|
+
import { RayHit, RayQuery, CharacterMove } from '../chunk-HF5HPS4T.mjs';
|
|
18
|
+
import { Vec3, Quat } from '../chunk-XPG3N5CF.mjs';
|
|
19
|
+
import { mat4ComposeArrays, mat4Compose, quatSlerpArrays } from '../chunk-KIZOA5LX.mjs';
|
|
20
|
+
import '../chunk-7SXOHAIR.mjs';
|
|
21
|
+
import '../chunk-ATAWRACV.mjs';
|
|
22
|
+
import '../chunk-T3AFF3L7.mjs';
|
|
23
|
+
import { createGpuStats, GPU_PASS_NAMES } from '../chunk-TL2SVO4P.mjs';
|
|
24
|
+
import { FRAME_NOW_MS_SLOT, FrameTime, FRAME_ALPHA_SLOT, FRAME_STEP_SECONDS_SLOT } from '../chunk-IDLW4NWA.mjs';
|
|
25
|
+
import '../chunk-73SGZLAQ.mjs';
|
|
26
|
+
import '../chunk-M5EXQZYY.mjs';
|
|
27
|
+
import '../chunk-TU5RYTYU.mjs';
|
|
28
|
+
import '../chunk-EIB4OWVQ.mjs';
|
|
29
|
+
import '../chunk-PBL6YL5L.mjs';
|
|
30
|
+
import '../chunk-E65WCTIG.mjs';
|
|
31
|
+
import '../chunk-5YFBGGCK.mjs';
|
|
32
|
+
import '../chunk-K4UDGE3Z.mjs';
|
|
33
|
+
import '../chunk-MOBSBXVE.mjs';
|
|
34
|
+
import '../chunk-SMK6JU2F.mjs';
|
|
35
|
+
import '../chunk-JURRXY2J.mjs';
|
|
36
|
+
import '../chunk-GHS6MIL6.mjs';
|
|
37
|
+
import '../chunk-GDCV4IWO.mjs';
|
|
38
|
+
import '../chunk-Y66WVMO5.mjs';
|
|
39
|
+
import '../chunk-A33OORT7.mjs';
|
|
40
|
+
import '../chunk-LYEZZEEF.mjs';
|
|
41
|
+
import '../chunk-HAGEIC2L.mjs';
|
|
42
|
+
import '../chunk-K46SMZ66.mjs';
|
|
43
|
+
import '../chunk-OQRVEM56.mjs';
|
|
44
|
+
import '../chunk-SPCRQ6NO.mjs';
|
|
45
|
+
import '../chunk-V7ZRH3PY.mjs';
|
|
46
|
+
import '../chunk-MOMOZEUM.mjs';
|
|
47
|
+
import '../chunk-N4RGGDEL.mjs';
|
|
48
|
+
import { GameError } from '../chunk-XJIHN3BY.mjs';
|
|
49
|
+
import '../chunk-25T4JHEA.mjs';
|
|
50
|
+
import { describe, it, expect } from 'vitest';
|
|
51
|
+
import { execFileSync } from 'child_process';
|
|
52
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'fs';
|
|
53
|
+
import { tmpdir } from 'os';
|
|
54
|
+
import { join } from 'path';
|
|
55
|
+
|
|
56
|
+
// src/game/conformance/harness.ts
|
|
57
|
+
var SUSTAINED_BODIES = 48, SUSTAINED_INSTANCES = 512, SUSTAINED_SEED = 9, BOB_RATE = 0.05, KEY_TOGGLE_PERIOD = 7, FRAMES_PER_STAMP_CYCLE = 3, MS_PER_STAMP_CYCLE = 50, INSTANCE_POSITION = 0, INSTANCE_ROTATION = 3, INSTANCE_SCALE = 7, INSTANCE_POSE = 10, INSTANCE_ROW = 32, BODY_ROW = 8, GROUND_HALF_EXTENT = 50, SilentUiHost = class {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.publishes = 0;
|
|
60
|
+
}
|
|
61
|
+
declareHud(_layout) {
|
|
62
|
+
}
|
|
63
|
+
publishHud(_numbers, _texts, _dirty, _dirtyCount) {
|
|
64
|
+
this.publishes++;
|
|
65
|
+
}
|
|
66
|
+
showPanel(_panel, _props) {
|
|
67
|
+
}
|
|
68
|
+
hidePanel(_panel) {
|
|
69
|
+
}
|
|
70
|
+
haptic(_pattern) {
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function frameStamp(frame) {
|
|
74
|
+
return frame * (50) / (3) | 0;
|
|
75
|
+
}
|
|
76
|
+
function spawnAt(game, x, y, z) {
|
|
77
|
+
let e = game.world.spawn();
|
|
78
|
+
return game.transforms.add(e, x, y, z), e;
|
|
79
|
+
}
|
|
80
|
+
async function createSustainedHarness(backend, vendorQueries) {
|
|
81
|
+
let game = await createGame({ backend, seed: (9), ui: new SilentUiHost() }), floor = spawnAt(game, 0, -0.5, 0);
|
|
82
|
+
game.physics.addCollider(floor, { shape: { kind: "cuboid", halfExtents: [(50), 0.5, (50)] } });
|
|
83
|
+
let gfx = game.graphics, geometry = gfx.createGeometry(sphereGeometry(0.25, 8, 6)), material = gfx.createMaterial({ kind: "standard" }), camera = spawnAt(game, 0, 3, 10);
|
|
84
|
+
gfx.camera(camera), game.audio.setListener(camera);
|
|
85
|
+
let bodies = [], bodyKind = vendorQueries ? "dynamic" : "kinematic";
|
|
86
|
+
for (let i = 0; i < (48); i++) {
|
|
87
|
+
let e = spawnAt(game, i % (8) - (8) / 2, 1 + Math.floor(i / (8)), 0);
|
|
88
|
+
game.physics.addBody(e, { kind: bodyKind, canSleep: false }), game.physics.addCollider(e, { shape: { kind: "ball", radius: 0.25 }, restitution: 0.9 }), gfx.mesh(e, geometry, material), bodies.push(e);
|
|
89
|
+
}
|
|
90
|
+
let positions = game.transforms.store.fields.position, kinematic = spawnAt(game, 0, 0.6, 3);
|
|
91
|
+
game.physics.addBody(kinematic, { kind: "kinematic" });
|
|
92
|
+
let kinematicCollider = game.physics.addCollider(kinematic, { shape: { kind: "capsule", halfHeight: 0.3, radius: 0.3 } }), controller = game.physics.createCharacterController({ snapToGround: 0.2 }), batch = gfx.instanced({ geometry, material, capacity: (512), colors: true }), jump = game.input.defineButton("jump", [{ device: "key", code: "Space" }]), move = game.input.defineAxis("move", [{ device: "keys", negative: "KeyA", positive: "KeyD" }]), score = game.ui.hudNumber("score"), pulse = game.animation.createValue(0);
|
|
93
|
+
game.animation.tweenValue(pulse, 1, 0.5, Ease.InOutSine, 0, Repeat.PingPong), game.animation.tween(bodies[0], TweenTarget.ScaleUniform, 1.5, 0.75, Ease.OutBack, 0, Repeat.Loop);
|
|
94
|
+
let Pos = game.net.defineMessage("pos", {
|
|
95
|
+
id: "u16",
|
|
96
|
+
x: quantized(-50, 50, 16),
|
|
97
|
+
y: quantized(-50, 50, 16),
|
|
98
|
+
z: quantized(-50, 50, 16)
|
|
99
|
+
}), [local, remote] = LoopbackTransport.pair();
|
|
100
|
+
game.net.connect(local), remote.open("state", "latest");
|
|
101
|
+
let channel = game.net.channel("state", "latest"), reader = game.net.reader(), writer = new MessageWriter(), fx = Pos.field("x"), fy = Pos.field("y"), fz = Pos.field("z"), fid = Pos.field("id"), hit = new RayHit(), ray = new RayQuery().set(0, 10, 0, 0, -1, 0, 20), characterMove = new CharacterMove(), desired = new Vec3(), hop = new Vec3(0, 2, 0), position = new Vec3(), sound = await game.audio.load({ bytes: new ArrayBuffer(8) }), emitter = game.audio.playAt(bodies[1], sound, game.audio.effects, 1, 1, true), query = game.world.query(game.transforms.store.type), instance = new Float32Array((10));
|
|
102
|
+
instance[(3) + 3] = 1, instance[(7)] = 1, instance[(7) + 1] = 1, instance[(7) + 2] = 1;
|
|
103
|
+
let sink = new Float64Array(1), bob = (g) => {
|
|
104
|
+
for (let i = 0; i < (48); i++) {
|
|
105
|
+
let row = g.transforms.row(bodies[i]);
|
|
106
|
+
positions[row * 3 + 1] = 1 + Math.sin(g.tick * (0.05) + i);
|
|
107
|
+
}
|
|
108
|
+
}, vendor = (g) => {
|
|
109
|
+
let dt = g.clock[FRAME_STEP_SECONDS_SLOT];
|
|
110
|
+
g.input.justPressed(jump) && g.physics.applyImpulse(bodies[g.tick % (48)], hop), g.physics.moveCharacter(controller, kinematicCollider, desired.set(g.input.value(move) * dt, -dt, 0), characterMove), g.physics.raycast(ray, hit) && (sink[0] += hit.distance);
|
|
111
|
+
for (let i = 0; i < g.physics.contactCount(); i++) sink[0] += g.physics.contactColliderA(i);
|
|
112
|
+
};
|
|
113
|
+
game.addSystem({
|
|
114
|
+
fixedUpdate: vendorQueries ? vendor : bob,
|
|
115
|
+
update: (g) => {
|
|
116
|
+
let m = batch.matrices;
|
|
117
|
+
instance[(0) + 1] = g.animation.value(pulse);
|
|
118
|
+
for (let i = 0; i < (512); i++)
|
|
119
|
+
instance[(0)] = i % (32), instance[(0) + 2] = i / (32) | 0, mat4ComposeArrays(m, i * 16, instance, (0), instance, (3), instance, (7));
|
|
120
|
+
for (g.graphics.setInstanceCount(batch, (512)), g.graphics.markInstancesDirty(batch), g.ui.setNumber(score, g.tick), g.transforms.position(bodies[2], position), writer.begin(Pos).set(fid, 2).setVec3(fx, fy, fz, position), channel.send(writer), remote.send(0, writer.buffer, writer.length); channel.receive(reader); ) sink[0] += reader.get(fy);
|
|
121
|
+
sink[0] += query.update(), sink[0] += g.audio.isPlaying(emitter) ? 1 : 0;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
let keys = ["Space", "KeyD", "KeyA"];
|
|
125
|
+
return {
|
|
126
|
+
game,
|
|
127
|
+
frame(index) {
|
|
128
|
+
let key = keys[index % keys.length];
|
|
129
|
+
index % (7) === 0 ? game.input.keyEvent(key, true, index) : index % (7) === 1 && game.input.keyEvent(key, false, index), game.clock[FRAME_NOW_MS_SLOT] = frameStamp(index), game.runFrame();
|
|
130
|
+
},
|
|
131
|
+
sink() {
|
|
132
|
+
return sink[0];
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/game/conformance/load.ts
|
|
138
|
+
var OS_MODULE = "node:os";
|
|
139
|
+
var TIMING_BUDGETS_ENV = "CIMPLIFY_TIMING_BUDGETS", ADVISORY_TIMING_BUDGETS = "advisory";
|
|
140
|
+
function timingBudgetsAdvisory() {
|
|
141
|
+
return globalThis.process?.env?.[TIMING_BUDGETS_ENV] === ADVISORY_TIMING_BUDGETS;
|
|
142
|
+
}
|
|
143
|
+
function isOs(value) {
|
|
144
|
+
let os = value;
|
|
145
|
+
return typeof os?.availableParallelism == "function" && typeof os.loadavg == "function";
|
|
146
|
+
}
|
|
147
|
+
async function busyMachine() {
|
|
148
|
+
let os;
|
|
149
|
+
try {
|
|
150
|
+
os = await import(
|
|
151
|
+
/* @vite-ignore */
|
|
152
|
+
OS_MODULE
|
|
153
|
+
);
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (!isOs(os)) return null;
|
|
158
|
+
let cores = os.availableParallelism(), load = os.loadavg()[0];
|
|
159
|
+
return timingBudgetsAdvisory() ? { load, cores, advisory: true } : load > cores ? { load, cores, advisory: false } : null;
|
|
160
|
+
}
|
|
161
|
+
function loadSkipNotice(busy) {
|
|
162
|
+
return busy.advisory ? `::warning::timing budget is advisory on this runner (${TIMING_BUDGETS_ENV}=${ADVISORY_TIMING_BUDGETS}, 1-minute load ${busy.load.toFixed(2)} on ${busy.cores} cores); compare the measured numbers against the budgets` : `budget gate skipped: 1-minute load average ${busy.load.toFixed(2)} exceeds ${busy.cores} cores; rerun on a quiet machine`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/game/conformance/microbench.ts
|
|
166
|
+
var DEFAULT_MICROBENCH_BUDGETS_NS = {
|
|
167
|
+
mat4Compose: 50,
|
|
168
|
+
slerp: 200,
|
|
169
|
+
transformPerEntity: 200,
|
|
170
|
+
queryPerEntity: 30,
|
|
171
|
+
codecField: 100,
|
|
172
|
+
raycast: 5e3,
|
|
173
|
+
physicsStepPerBody: 1e4
|
|
174
|
+
}, MATH_ITERATIONS = 5e5, FIELD_ITERATIONS = 5e5, ENTITY_PASSES = 100, RAYCASTS = 2e3, STEP_BODIES = 256, STEP_PASSES = 120, STEP_SETTLE = 60, STEP_ROW = 16, STEP_LAYERS = 3, STEP_SPACING = 1.1, STEP_BASE_HEIGHT = 0.6, STEP_RADIUS = 0.5, STEP_GROUND_HALF_EXTENT = 100, ENTITIES = 1e3, WARMUP_PASSES = 2, WARMUP_MIN_MS = 200, WARMUP_MAX_MS = 3e3, SETTLED_RATIO = 0.95, SAMPLED_PASSES = 4, MIN_SAMPLES = 64, SAMPLE_TARGET_NS = 1e5, SAMPLING_MIN_MS = 300, SAMPLING_MAX_MS = 2e3, NS_PER_MS = 1e6, POSE = [1, 2, 3, 0, 0, 0, 1, 1, 1, 1], ROTATIONS = [0, 0, 0, 1, 0, Math.SQRT1_2, 0, Math.SQRT1_2];
|
|
175
|
+
function time(iterations, body) {
|
|
176
|
+
let i = 0, probe = Number.POSITIVE_INFINITY, warmupStart = performance.now();
|
|
177
|
+
for (let pass = 0; ; pass++) {
|
|
178
|
+
let passStart = performance.now();
|
|
179
|
+
for (let k = 0; k < iterations; k++) body(i++);
|
|
180
|
+
let now = performance.now(), perIteration = (now - passStart) * (1e6) / iterations, improving = perIteration < probe * (0.95);
|
|
181
|
+
perIteration < probe && (probe = perIteration);
|
|
182
|
+
let warm = now - warmupStart;
|
|
183
|
+
if (warm >= (3e3) || pass + 1 >= (2) && warm >= (200) && !improving) break;
|
|
184
|
+
}
|
|
185
|
+
let batch = Math.min(iterations, Math.max(1, Math.ceil((1e5) / Math.max(probe, Number.MIN_VALUE)))), samples = Math.max((64), Math.ceil((4) * iterations / batch)), floor = Number.POSITIVE_INFINITY, samplingStart = performance.now();
|
|
186
|
+
for (let sample = 0; ; sample++) {
|
|
187
|
+
let start = performance.now();
|
|
188
|
+
for (let k = 0; k < batch; k++) body(i++);
|
|
189
|
+
let end = performance.now(), perIteration = (end - start) * (1e6) / batch;
|
|
190
|
+
perIteration < floor && (floor = perIteration);
|
|
191
|
+
let sampled = end - samplingStart;
|
|
192
|
+
if (sampled >= (2e3) || sample + 1 >= samples && sampled >= (300)) break;
|
|
193
|
+
}
|
|
194
|
+
return floor;
|
|
195
|
+
}
|
|
196
|
+
async function runMicrobench(createBackend) {
|
|
197
|
+
let out = new Float32Array(64), pose = Float32Array.from(POSE), rotations = Float32Array.from(ROTATIONS), weight = new Float64Array([0.5]), mat4Compose2 = time((5e5), (i) => mat4ComposeArrays(out, (i & 3) * 16, pose, 0, pose, 3, pose, 7)), slerp = time((5e5), (i) => quatSlerpArrays(out, (i & 3) * 4, rotations, 0, rotations, 4, weight, 0)), game = await createGame({ backend: createBackend() }), ground2 = game.world.spawn();
|
|
198
|
+
game.transforms.add(ground2, 0, -0.5, 0), game.physics.addCollider(ground2, { shape: { kind: "cuboid", halfExtents: [50, 0.5, 50] } });
|
|
199
|
+
for (let i = 0; i < (1e3); i++) {
|
|
200
|
+
let e = game.world.spawn();
|
|
201
|
+
game.transforms.add(e, i, 0, 0);
|
|
202
|
+
}
|
|
203
|
+
let frame = new FrameTime();
|
|
204
|
+
frame.values[FRAME_ALPHA_SLOT] = 0.5;
|
|
205
|
+
let transformPerEntity = time((100), () => game.transforms.computeRender(frame)) / (1e3), query = game.world.query(game.transforms.store.type), queryPerEntity = time((100), () => query.update()) / (1e3), message = game.net.defineMessage("bench", { x: quantized(-10, 10, 12) }), writer = new MessageWriter();
|
|
206
|
+
writer.begin(message);
|
|
207
|
+
let field = message.field("x"), codecField = time((5e5), (i) => writer.set(field, (i & 15) - 8));
|
|
208
|
+
game.advance(1);
|
|
209
|
+
let hit = new RayHit(), ray = new RayQuery().set(0, 5, 0, 0, -1, 0, 10), raycast = time((2e3), () => game.physics.raycast(ray, hit));
|
|
210
|
+
game.dispose();
|
|
211
|
+
let physicsStepPerBody = await stepCost(createBackend);
|
|
212
|
+
return { mat4Compose: mat4Compose2, slerp, transformPerEntity, queryPerEntity, codecField, raycast, physicsStepPerBody };
|
|
213
|
+
}
|
|
214
|
+
async function stepCost(createBackend) {
|
|
215
|
+
let game = await createGame({ backend: createBackend() }), ground2 = game.world.spawn();
|
|
216
|
+
game.transforms.add(ground2, 0, -0.5, 0), game.physics.addCollider(ground2, { shape: { kind: "cuboid", halfExtents: [(100), 0.5, (100)] } });
|
|
217
|
+
for (let i = 0; i < (256); i++) {
|
|
218
|
+
let e = game.world.spawn();
|
|
219
|
+
game.transforms.add(e, i % (16) * (1.1), (0.6) + i % (3) * (1.1), (i / (16) | 0) * (1.1)), game.physics.addBody(e, { kind: "dynamic", canSleep: false }), game.physics.addCollider(e, { shape: { kind: "ball", radius: (0.5) } });
|
|
220
|
+
}
|
|
221
|
+
for (let i = 0; i < (60); i++) game.physics.step();
|
|
222
|
+
let perBody = time((120), () => game.physics.step()) / (256);
|
|
223
|
+
return game.dispose(), perBody;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/game/conformance/suite.ts
|
|
227
|
+
async function measureSustainedAllocation(createBackend, heap, vendorQueries, sustainedFrames = (6e3), freshIsolate = false) {
|
|
228
|
+
let meter = await heap();
|
|
229
|
+
if (!meter) return null;
|
|
230
|
+
let harness = await createSustainedHarness(createBackend(), vendorQueries), start = (3e4);
|
|
231
|
+
if (freshIsolate) {
|
|
232
|
+
for (let i = 0; i < (3e4); i++) harness.frame(i);
|
|
233
|
+
collectYoung();
|
|
234
|
+
for (let block = 0; block < (8); block++) {
|
|
235
|
+
let settled = await measureAllocations(meter, (3e3), (i) => harness.frame(start + i));
|
|
236
|
+
if (start += (3e3), settled.bytes === 0) break;
|
|
237
|
+
}
|
|
238
|
+
collectYoung();
|
|
239
|
+
} else await warmUp((3e4), (i) => harness.frame(i));
|
|
240
|
+
let report = await measureAllocations(meter, sustainedFrames, (i) => harness.frame(start + i)), longer = await measureAllocations(meter, sustainedFrames * 2, (i) => harness.frame(start + sustainedFrames + i));
|
|
241
|
+
if (meter.dispose(), harness.game.dispose(), !Number.isFinite(harness.sink())) throw new Error("the sustained harness produced a non-finite sink");
|
|
242
|
+
return [report, longer];
|
|
243
|
+
}
|
|
244
|
+
var DEFAULT_SUSTAINED_FRAMES = 6e3, WARMUP_FRAMES = 3e4, QUIESCE_BLOCKS = 8, QUIESCE_BLOCK_FRAMES = 3e3, QUIESCE_PAST_V8_FRAME_CHAIN_TIER_UP = true, FRAME_MS = 1e3 / 60, RESIDUE_BYTES_PER_FRAME = 1, MICROBENCH_TIMEOUT_MS = 12e4, RecordingUiHost = class {
|
|
245
|
+
constructor() {
|
|
246
|
+
this.layout = [];
|
|
247
|
+
this.publishes = 0;
|
|
248
|
+
this.lastDirty = 0;
|
|
249
|
+
this.panels = [];
|
|
250
|
+
this.haptics = [];
|
|
251
|
+
}
|
|
252
|
+
declareHud(layout) {
|
|
253
|
+
this.layout = layout;
|
|
254
|
+
}
|
|
255
|
+
publishHud(_numbers, _texts, _dirty, dirtyCount) {
|
|
256
|
+
this.publishes++, this.lastDirty = dirtyCount;
|
|
257
|
+
}
|
|
258
|
+
showPanel(panel) {
|
|
259
|
+
this.panels.push(panel);
|
|
260
|
+
}
|
|
261
|
+
hidePanel(panel) {
|
|
262
|
+
this.panels.splice(this.panels.indexOf(panel), 1);
|
|
263
|
+
}
|
|
264
|
+
haptic(pattern) {
|
|
265
|
+
this.haptics.push(pattern);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function emptyStats() {
|
|
269
|
+
return { nodes: 0, geometries: 0, materials: 0, textures: 0, renderTargets: 0, postChains: 0, renderPasses: 0, frames: 0 };
|
|
270
|
+
}
|
|
271
|
+
function spawnAt2(game, x, y, z) {
|
|
272
|
+
let e = game.world.spawn();
|
|
273
|
+
return game.transforms.add(e, x, y, z), e;
|
|
274
|
+
}
|
|
275
|
+
function ground(game) {
|
|
276
|
+
let e = spawnAt2(game, 0, -0.5, 0);
|
|
277
|
+
return game.physics.addCollider(e, { shape: { kind: "cuboid", halfExtents: [50, 0.5, 50] } }), e;
|
|
278
|
+
}
|
|
279
|
+
function hostClocks() {
|
|
280
|
+
let clocks = [];
|
|
281
|
+
for (let scope of [globalThis, Reflect.get(globalThis, "self"), Reflect.get(globalThis, "window")]) {
|
|
282
|
+
if (typeof scope != "object" || scope === null) continue;
|
|
283
|
+
let clock = Reflect.get(scope, "performance");
|
|
284
|
+
clock instanceof Object && clocks.indexOf(clock) < 0 && clocks.push(clock);
|
|
285
|
+
}
|
|
286
|
+
return clocks;
|
|
287
|
+
}
|
|
288
|
+
function withFrozenHostClock(run2) {
|
|
289
|
+
let clocks = hostClocks(), saved = clocks.map((clock) => Object.getOwnPropertyDescriptor(clock, "now"));
|
|
290
|
+
for (let clock of clocks) Object.defineProperty(clock, "now", { configurable: true, writable: true, value: frozenNow });
|
|
291
|
+
return run2().finally(() => {
|
|
292
|
+
for (let i = 0; i < clocks.length; i++) {
|
|
293
|
+
let own = saved[i];
|
|
294
|
+
own ? Object.defineProperty(clocks[i], "now", own) : Reflect.deleteProperty(clocks[i], "now");
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function defineGameConformanceSuite(options) {
|
|
299
|
+
let make = (seed = 1, ui = null) => createGame({ backend: options.createBackend(), seed, ui });
|
|
300
|
+
describe(`game API conformance: ${options.name}`, () => {
|
|
301
|
+
describe("entities and components", () => {
|
|
302
|
+
it("reuses indices with new generations and invalidates stale handles", () => {
|
|
303
|
+
let world = new World(8), a = world.spawn();
|
|
304
|
+
expect(world.despawn(a)).toBe(true);
|
|
305
|
+
let b = world.spawn();
|
|
306
|
+
expect(b).not.toBe(a), expect(world.isAlive(a)).toBe(false), expect(world.isAlive(b)).toBe(true), expect(world.despawn(a)).toBe(false);
|
|
307
|
+
}), it("stores fields as structure of arrays and swap-removes", () => {
|
|
308
|
+
let Health = defineComponent("conformance-health", { hp: "f32", armor: ["u8", 2] }), world = new World(16), store = world.register(Health), e = [];
|
|
309
|
+
for (let i = 0; i < 4; i++) {
|
|
310
|
+
e.push(world.spawn());
|
|
311
|
+
let row = world.add(e[i], Health);
|
|
312
|
+
store.fields.hp[row] = i * 10, store.fields.armor[row * 2 + 1] = i;
|
|
313
|
+
}
|
|
314
|
+
world.remove(e[1], Health), expect(store.count).toBe(3);
|
|
315
|
+
let moved = store.row(e[3]);
|
|
316
|
+
expect(store.fields.hp[moved]).toBe(30), expect(store.fields.armor[moved * 2 + 1]).toBe(3), expect(store.has(e[1])).toBe(false);
|
|
317
|
+
}), it("queries the intersection of components", () => {
|
|
318
|
+
let A = defineComponent("conformance-a", { v: "i32" }), B = defineComponent("conformance-b", { v: "i32" }), world = new World(32), q = world.query(A, B);
|
|
319
|
+
for (let i = 0; i < 10; i++) {
|
|
320
|
+
let e = world.spawn();
|
|
321
|
+
world.add(e, A), i % 2 === 0 && world.add(e, B);
|
|
322
|
+
}
|
|
323
|
+
expect(q.update()).toBe(5);
|
|
324
|
+
for (let i = 0; i < q.count; i++) expect(world.has(q.entityAt(i), B)).toBe(true);
|
|
325
|
+
}), it("refuses growth past declared capacity", () => {
|
|
326
|
+
let world = new World(2);
|
|
327
|
+
world.spawn(), world.spawn(), expect(() => world.spawn()).toThrow(GameError);
|
|
328
|
+
});
|
|
329
|
+
}), describe("loop, scenes and transforms", () => {
|
|
330
|
+
it("runs fixed steps with interpolation and clamps runaway frames", async () => {
|
|
331
|
+
let game = await make();
|
|
332
|
+
game.frame(0), game.frame(FRAME_MS * 2.5), expect(game.tick).toBe(2), expect(game.alpha).toBeCloseTo(0.5, 5), game.frame(FRAME_MS * 2.5 + 1e4), expect(game.tick).toBe(2 + game.loop.maxSubsteps), game.dispose();
|
|
333
|
+
}), it("switches scenes at step boundaries with enter and exit", async () => {
|
|
334
|
+
let game = await make(), log = [];
|
|
335
|
+
game.addScene({ name: "a", enter: () => log.push("enter a"), exit: () => log.push("exit a") }), game.addScene({ name: "b", enter: () => log.push("enter b"), fixedUpdate: () => log.push("step b") }), game.start("a"), game.scenes.switchTo("b"), game.advance(1), expect(log).toEqual(["enter a", "exit a", "enter b", "step b"]), game.dispose();
|
|
336
|
+
}), it("interpolates render matrices and composes hierarchies", async () => {
|
|
337
|
+
let game = await make(), parent = spawnAt2(game, 0, 0, 0), child = spawnAt2(game, 1, 0, 0);
|
|
338
|
+
game.transforms.setParent(child, parent), game.advance(1), game.transforms.snapshot(), game.transforms.setPosition(parent, 10, 0, 0), game.transforms.computeRender(halfway());
|
|
339
|
+
let out = new Vec3();
|
|
340
|
+
expect(game.transforms.renderPosition(parent, out).x).toBeCloseTo(5, 5), expect(game.transforms.renderPosition(child, out).x).toBeCloseTo(6, 5), game.dispose();
|
|
341
|
+
});
|
|
342
|
+
}), describe("graphics", () => {
|
|
343
|
+
it("creates every resource family and mirrors entity matrices onto nodes", async () => {
|
|
344
|
+
let game = await make(), gfx = game.graphics, geometry = gfx.createGeometry(boxGeometry(1, 2, 3)), material = gfx.createMaterial({ kind: "standard", color: 16711680, roughness: 0.5 }), e = spawnAt2(game, 1, 2, 3), node = gfx.mesh(e, geometry, material), camera = spawnAt2(game, 0, 1, 5);
|
|
345
|
+
gfx.camera(camera);
|
|
346
|
+
let light = spawnAt2(game, 0, 5, 0);
|
|
347
|
+
gfx.create(light, { kind: "light", light: "directional", intensity: 2 }), gfx.create(spawnAt2(game, 0, 0, 0), { kind: "light", light: "point", intensity: 1, distance: 10 }), gfx.create(spawnAt2(game, 0, 0, 0), { kind: "group" }), gfx.setEnvironment({ background: { color: 1052688 }, toneMapping: "aces", exposure: 1 }), game.advance(1);
|
|
348
|
+
let m = new Float32Array(16);
|
|
349
|
+
gfx.readNodeMatrix(node, m), expect(m[12]).toBeCloseTo(1, 5), expect(m[13]).toBeCloseTo(2, 5), expect(m[14]).toBeCloseTo(3, 5);
|
|
350
|
+
let stats = emptyStats();
|
|
351
|
+
gfx.stats(stats), expect(stats.nodes).toBeGreaterThanOrEqual(5), expect(stats.frames).toBeGreaterThan(0), game.world.despawn(e), gfx.stats(stats), expect(stats.nodes).toBeGreaterThanOrEqual(4), game.dispose();
|
|
352
|
+
}), it("updates custom vertex buffers and instance buffers in place", async () => {
|
|
353
|
+
let game = await make(), gfx = game.graphics, positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), geometry = gfx.createGeometry({ attributes: { position: { data: positions, itemSize: 3, dynamic: true } } });
|
|
354
|
+
expect(gfx.geometryAttribute(geometry, "position")).toBe(positions), positions[0] = 5, gfx.markGeometryDirty(geometry, "position", 0, 3);
|
|
355
|
+
let material = gfx.createMaterial({ kind: "unlit", color: 16777215 }), batch = gfx.instanced({ geometry: gfx.createGeometry(sphereGeometry(0.5, 8, 4)), material, capacity: 16, colors: true });
|
|
356
|
+
expect(batch.matrices.length).toBe(256), expect(batch.colors?.length).toBe(48);
|
|
357
|
+
for (let i = 0; i < 4; i++) mat4Compose(batch.matrices, i * 16, i, 0, 0, 0, 0, 0, 1, 1, 1, 1);
|
|
358
|
+
gfx.setInstanceCount(batch, 4), gfx.markInstancesDirty(batch), expect(() => gfx.setInstanceCount(batch, 17)).toThrow(GameError), game.dispose();
|
|
359
|
+
}), it("drives morph targets and skeletons", async () => {
|
|
360
|
+
let game = await make(), gfx = game.graphics, base = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), geometry = gfx.createGeometry({
|
|
361
|
+
attributes: {
|
|
362
|
+
position: { data: base, itemSize: 3 },
|
|
363
|
+
skinIndex: { data: new Uint16Array(12), itemSize: 4 },
|
|
364
|
+
skinWeight: { data: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]), itemSize: 4 }
|
|
365
|
+
},
|
|
366
|
+
morphTargets: [{ name: "smile", position: new Float32Array([0, 0.1, 0, 0, 0.1, 0, 0, 0.1, 0]) }]
|
|
367
|
+
}), material = gfx.createMaterial({ kind: "standard" }), morphNode = gfx.mesh(spawnAt2(game, 0, 0, 0), geometry, material), weights = gfx.morphWeights(morphNode);
|
|
368
|
+
expect(weights.length).toBe(1), weights[0] = 0.5, gfx.markMorphWeightsDirty(morphNode);
|
|
369
|
+
let bindPose = new Float32Array(20);
|
|
370
|
+
bindPose[6] = 1, bindPose[7] = 1, bindPose[8] = 1, bindPose[9] = 1, bindPose[11] = 1, bindPose[16] = 1, bindPose[17] = 1, bindPose[18] = 1, bindPose[19] = 1;
|
|
371
|
+
let skinned = gfx.create(spawnAt2(game, 0, 0, 0), {
|
|
372
|
+
kind: "skinned",
|
|
373
|
+
geometry,
|
|
374
|
+
material,
|
|
375
|
+
skeleton: { parents: new Int16Array([-1, 0]), bindPose }
|
|
376
|
+
}), pose = gfx.skeletonPose(skinned);
|
|
377
|
+
expect(pose.length).toBe(20), pose[11] = 2, gfx.markSkeletonDirty(skinned), game.advance(1), game.dispose();
|
|
378
|
+
}), it("compiles shader materials through the backend's shader language", async () => {
|
|
379
|
+
let game = await make(), gfx = game.graphics, wgslOnly = { wgsl: { fragment: "@fragment fn main() -> @location(0) vec4f { return vec4f(1.0); }" } };
|
|
380
|
+
gfx.capabilities.shaderLanguages.indexOf("wgsl") < 0 && expect(() => gfx.createMaterial({ kind: "shader", shader: wgslOnly })).toThrow(GameError);
|
|
381
|
+
let material = gfx.createMaterial({
|
|
382
|
+
kind: "shader",
|
|
383
|
+
shader: {
|
|
384
|
+
wgsl: wgslOnly.wgsl,
|
|
385
|
+
glsl: {
|
|
386
|
+
vertex: "void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }",
|
|
387
|
+
fragment: "uniform float glow; uniform vec3 tint; void main() { gl_FragColor = vec4(tint * glow, 1.0); }"
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
uniforms: { glow: { type: "f32", value: 1 }, tint: { type: "vec3", value: new Float32Array([1, 0, 0]) } }
|
|
391
|
+
});
|
|
392
|
+
gfx.setUniform(material, "glow", 2), gfx.setUniformVector(material, "tint", new Float32Array([0, 1, 0])), expect(() => gfx.setUniform(material, "missing", 1)).toThrow(GameError), game.dispose();
|
|
393
|
+
}), it("builds render targets, passes and post-processing chains", async () => {
|
|
394
|
+
let game = await make(), gfx = game.graphics, camera = spawnAt2(game, 0, 0, 5), cameraNode = gfx.camera(camera), target = gfx.createRenderTarget({ width: 128, height: 128, format: "rgba16f" }), texture = gfx.renderTargetTexture(target);
|
|
395
|
+
expect(texture).toBeGreaterThan(0);
|
|
396
|
+
let pass = gfx.createRenderPass({ camera: cameraNode, target, layers: 1, clearColor: 0, order: -1 });
|
|
397
|
+
gfx.setRenderPassEnabled(pass, false);
|
|
398
|
+
let chain = gfx.createPostChain({
|
|
399
|
+
effects: [{ kind: "bloom", strength: 0.8 }, { kind: "fxaa" }, { kind: "vignette" }, { kind: "color_grade", contrast: 1.1 }]
|
|
400
|
+
});
|
|
401
|
+
gfx.setPostChain(chain), gfx.setPostUniform(chain, 3, "contrast", 1.2), game.advance(1);
|
|
402
|
+
let stats = emptyStats();
|
|
403
|
+
gfx.stats(stats), expect(stats.renderTargets).toBe(1), expect(stats.postChains).toBe(1), expect(stats.renderPasses).toBe(1), expect(gfx.gpu === null).toBe(!gfx.capabilities.compute), game.dispose();
|
|
404
|
+
}), it("expresses toon, matcap, anisotropy, fat lines, area and probe lights, LOD, sky and GPU stats", async () => {
|
|
405
|
+
let game = await make(), gfx = game.graphics, caps = gfx.capabilities, geometry = gfx.createGeometry(boxGeometry(1, 1, 1)), ramp = gfx.createTexture({ source: { kind: "pixels", width: 2, height: 1, data: new Uint8Array([64, 64, 64, 255, 255, 255, 255, 255]) } }), toon = gfx.createMaterial({ kind: "toon", color: 3385941, gradientMap: ramp }), matcap = gfx.createMaterial({ kind: "matcap", matcap: ramp }), brushed = gfx.createMaterial({ kind: "physical", metalness: 1, roughness: 0.3, anisotropy: 0.8, anisotropyRotation: 0.5 });
|
|
406
|
+
gfx.setMaterialTexture(toon, "gradientMap", ramp);
|
|
407
|
+
let near = gfx.mesh(spawnAt2(game, 0, 0, -2), geometry, toon), far = gfx.mesh(spawnAt2(game, 0, 0, -2), geometry, matcap);
|
|
408
|
+
gfx.mesh(spawnAt2(game, 2, 0, -2), geometry, brushed), gfx.lodLevels([
|
|
409
|
+
{ node: near, distance: 0 },
|
|
410
|
+
{ node: far, distance: 20 }
|
|
411
|
+
]);
|
|
412
|
+
let line = gfx.createGeometry({ attributes: { position: { data: new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0]), itemSize: 3 } } });
|
|
413
|
+
gfx.create(spawnAt2(game, 0, 0, 0), { kind: "lines", geometry: line, material: gfx.createMaterial({ kind: "lines", width: 3, dashSize: 0.1, gapSize: 0.05 }) }), gfx.create(spawnAt2(game, 0, 3, 0), { kind: "light", light: "rectArea", width: 2, height: 1, intensity: 4 }), gfx.create(spawnAt2(game, 0, 0, 0), { kind: "light", light: "probe", sh: new Float32Array(27).fill(0.1) }), gfx.create(spawnAt2(game, 0, 4, 0), {
|
|
414
|
+
kind: "light",
|
|
415
|
+
light: "directional",
|
|
416
|
+
castShadow: true,
|
|
417
|
+
shadow: { type: "pcss", cascades: 2 }
|
|
418
|
+
}), gfx.camera(spawnAt2(game, 0, 1, 5)), gfx.setEnvironment({ sky: { turbidity: 6, clouds: { coverage: 0.5 } }, background: { sky: true }, toneMapping: "agx" }), gfx.setTimeOfDay(17, 30, 120), game.advance(2);
|
|
419
|
+
let stats = createGpuStats();
|
|
420
|
+
gfx.gpuStats(stats), expect(stats.passNs.length).toBe(GPU_PASS_NAMES.length), expect(typeof caps.lod).toBe("boolean");
|
|
421
|
+
let counts = emptyStats();
|
|
422
|
+
gfx.stats(counts), expect(counts.materials).toBeGreaterThanOrEqual(4), game.dispose();
|
|
423
|
+
}), it("loads glTF models, finds nodes and plays their clips", async () => {
|
|
424
|
+
let game = await make(), gfx = game.graphics, model = await gfx.loadModel({ bytes: buildTriangleGlb() });
|
|
425
|
+
expect(model.nodeNames).toContain("Tri"), expect(model.clipNames).toEqual(["spin"]), expect(model.materialNames).toEqual(["Grass"]), expect(model.materials).toHaveLength(1);
|
|
426
|
+
let grass = model.materials[0];
|
|
427
|
+
gfx.setMaterialColor(grass, 3368473), gfx.setMaterialState(grass, { envMapIntensity: 0 });
|
|
428
|
+
let root = gfx.instantiate(spawnAt2(game, 0, 0, 0), model.id), tri = gfx.findNode(root, "Tri");
|
|
429
|
+
expect(tri).not.toBeNull();
|
|
430
|
+
let action = gfx.playClip(root, "spin", true);
|
|
431
|
+
game.advance(30);
|
|
432
|
+
let m = new Float32Array(16);
|
|
433
|
+
gfx.readNodeMatrix(tri, m), expect(Math.abs(m[0] - 1)).toBeGreaterThan(1e-3), gfx.stopClip(action), game.dispose();
|
|
434
|
+
});
|
|
435
|
+
}), describe("clip animation", () => {
|
|
436
|
+
let rig = async () => {
|
|
437
|
+
let game = await make(), gfx = game.graphics;
|
|
438
|
+
if (!gfx.hasClipAnimation)
|
|
439
|
+
return game.dispose(), null;
|
|
440
|
+
let model = await gfx.loadModel({ bytes: buildRigGlb() }), root = gfx.instantiate(spawnAt2(game, 0, 0, 0), model.id);
|
|
441
|
+
return { game, gfx, clips: gfx.clips, root, model, step: game.loop.stepSeconds };
|
|
442
|
+
}, hipsX = (gfx, root) => {
|
|
443
|
+
let m = new Float32Array(16);
|
|
444
|
+
return gfx.readNodeMatrix(gfx.findNode(root, RIG_HIPS), m), m[12];
|
|
445
|
+
};
|
|
446
|
+
it("reports clip info and seeks actions", async () => {
|
|
447
|
+
let r = await rig();
|
|
448
|
+
if (r === null) return;
|
|
449
|
+
let info = r.clips.clipInfo(r.root, "run");
|
|
450
|
+
expect(info.duration).toBeCloseTo(2, 5), expect(info.tracks).toBe(1);
|
|
451
|
+
let action = r.gfx.playClip(r.root, "run", true);
|
|
452
|
+
r.clips.setClipTime(action, 0.5), r.game.advance(1);
|
|
453
|
+
let state = emptyClipState();
|
|
454
|
+
r.clips.readClipState(action, state), expect(Math.abs(state.time - (0.5 + r.step))).toBeLessThan(1e-3), expect(state.running).toBe(true), r.game.dispose();
|
|
455
|
+
}), it("cross fades weights linearly", async () => {
|
|
456
|
+
let r = await rig();
|
|
457
|
+
if (r === null) return;
|
|
458
|
+
let low = r.gfx.playClip(r.root, "low", true), high = r.gfx.playClip(r.root, "high", true);
|
|
459
|
+
r.clips.crossFade(low, high, 1, false), r.game.advance(30);
|
|
460
|
+
let a = emptyClipState(), b = emptyClipState();
|
|
461
|
+
r.clips.readClipState(low, a), r.clips.readClipState(high, b), expect(Math.abs(a.weight - 0.5)).toBeLessThan(0.05), expect(Math.abs(b.weight - 0.5)).toBeLessThan(0.05), r.game.dispose();
|
|
462
|
+
}), it("fires clip events in order with loop notifications", async () => {
|
|
463
|
+
let r = await rig();
|
|
464
|
+
if (r === null) return;
|
|
465
|
+
r.clips.setClipEvents(r.root, "walk", [
|
|
466
|
+
{ time: 0.75, id: 9 },
|
|
467
|
+
{ time: 0.25, id: 7 }
|
|
468
|
+
]), r.gfx.playClip(r.root, "walk", true);
|
|
469
|
+
let buffer = [], ids = [], loops = 0;
|
|
470
|
+
for (let frame = 0; frame < 19; frame++) {
|
|
471
|
+
r.game.advance(6);
|
|
472
|
+
let count = r.clips.readClipEvents(buffer);
|
|
473
|
+
for (let i = 0; i < count; i++)
|
|
474
|
+
buffer[i].kind === "event" && ids.push(buffer[i].id), buffer[i].kind === "loop" && loops++;
|
|
475
|
+
}
|
|
476
|
+
expect(ids).toEqual([7, 9, 7, 9]), expect(loops).toBe(1), r.game.dispose();
|
|
477
|
+
}), it("keeps synced clips of different length in phase", async () => {
|
|
478
|
+
let r = await rig();
|
|
479
|
+
if (r === null) return;
|
|
480
|
+
let walk = r.gfx.playClip(r.root, "walk", true), run2 = r.gfx.playClip(r.root, "run", true);
|
|
481
|
+
r.clips.setClipSync(walk, 3), r.clips.setClipSync(run2, 3), r.gfx.setClipWeight(walk, 0.5), r.gfx.setClipWeight(run2, 0.5);
|
|
482
|
+
let a = emptyClipState(), b = emptyClipState();
|
|
483
|
+
for (let frame = 0; frame < 8; frame++)
|
|
484
|
+
r.game.advance(7), r.clips.readClipState(walk, a), r.clips.readClipState(run2, b), expect(Math.abs(a.time - b.time / 2)).toBeLessThan(1e-3);
|
|
485
|
+
expect(a.time).toBeGreaterThan(0), r.game.dispose();
|
|
486
|
+
}), it("weights 1D blend space members at a midpoint", async () => {
|
|
487
|
+
let r = await rig();
|
|
488
|
+
if (r === null) return;
|
|
489
|
+
let low = r.gfx.playClip(r.root, "low", true), high = r.gfx.playClip(r.root, "high", true), space = r.clips.createBlendSpace(r.root, {
|
|
490
|
+
kind: "1d",
|
|
491
|
+
members: [
|
|
492
|
+
{ action: low, x: 0 },
|
|
493
|
+
{ action: high, x: 1 }
|
|
494
|
+
]
|
|
495
|
+
});
|
|
496
|
+
r.clips.setBlendParameter(space, 0.5, 0, 1), r.game.advance(1);
|
|
497
|
+
let a = emptyClipState(), b = emptyClipState();
|
|
498
|
+
r.clips.readClipState(low, a), r.clips.readClipState(high, b), expect(Math.abs(a.weight - 0.5)).toBeLessThan(1e-3), expect(Math.abs(b.weight - 0.5)).toBeLessThan(1e-3), expect(Math.abs(hipsX(r.gfx, r.root) - 2)).toBeLessThan(1e-3), r.game.dispose();
|
|
499
|
+
}), it("maps Mixamo style names to the humanoid rig", async () => {
|
|
500
|
+
let r = await rig();
|
|
501
|
+
if (r === null) return;
|
|
502
|
+
let map = r.clips.humanoid(r.root);
|
|
503
|
+
expect(map.hips).toBe(RIG_HIPS), expect(map.spine).toBe("mixamorig_Spine"), expect(map.chest).toBe("mixamorig_Spine1"), r.game.dispose();
|
|
504
|
+
}), it("extracts root motion across loops", async () => {
|
|
505
|
+
let r = await rig();
|
|
506
|
+
if (r === null) return;
|
|
507
|
+
r.gfx.playClip(r.root, "walk", true);
|
|
508
|
+
let motion = r.clips.enableRootMotion(r.root, { bone: RIG_HIPS }), delta = new Float32Array(4), travelled = 0;
|
|
509
|
+
for (let frame = 0; frame < 25; frame++)
|
|
510
|
+
r.game.advance(3), r.clips.readRootMotion(motion, delta, 0), travelled += delta[0];
|
|
511
|
+
expect(Math.abs(travelled - 75 * r.step * (2))).toBeLessThan(0.01), expect(Math.abs(hipsX(r.gfx, r.root))).toBeLessThan(1e-4), r.game.dispose();
|
|
512
|
+
}), it("scales masked bones by fractional mask weights", async () => {
|
|
513
|
+
let r = await rig();
|
|
514
|
+
if (r === null) return;
|
|
515
|
+
let action = r.gfx.playClip(r.root, "high", true), mask = r.clips.createClipMask(r.root, { [RIG_HIPS]: 0.5 });
|
|
516
|
+
r.clips.setClipMask(action, mask), r.game.advance(2), expect(Math.abs(hipsX(r.gfx, r.root) - 2)).toBeLessThan(1e-3), r.clips.setClipMask(action, null), r.game.advance(2), expect(Math.abs(hipsX(r.gfx, r.root) - 4)).toBeLessThan(1e-3), r.game.dispose();
|
|
517
|
+
}), it("keeps cubic splines exact inside clip ranges", async () => {
|
|
518
|
+
let r = await rig();
|
|
519
|
+
if (r === null) return;
|
|
520
|
+
let other = r.gfx.instantiate(spawnAt2(r.game, 0, 0, 0), r.model.id), whole = r.gfx.playClip(r.root, "curve", true), ranged = r.gfx.playClip(other, "curve", true);
|
|
521
|
+
r.clips.setClipRange(ranged, 0.3, 1.7), r.clips.setClipPaused(whole, true), r.clips.setClipPaused(ranged, true);
|
|
522
|
+
for (let time2 of [0.3, 0.5, 1.2, 1.7])
|
|
523
|
+
r.clips.setClipTime(whole, time2), r.clips.setClipTime(ranged, time2), r.game.advance(1), expect(Math.abs(hipsX(r.gfx, r.root) - hipsX(r.gfx, other))).toBeLessThan(1e-4);
|
|
524
|
+
r.game.dispose();
|
|
525
|
+
});
|
|
526
|
+
}), describe("input", () => {
|
|
527
|
+
it("maps keys, gamepad and virtual controls to actions with step edges", async () => {
|
|
528
|
+
let game = await make(), input = game.input, jump = input.defineButton("jump", [{ device: "key", code: "Space" }, { device: "gamepad", button: 0 }]), move = input.defineAxis("move", [
|
|
529
|
+
{ device: "keys", negative: "KeyA", positive: "KeyD" },
|
|
530
|
+
{ device: "gamepad", axis: 0 },
|
|
531
|
+
{ device: "virtual", control: "stick-x" }
|
|
532
|
+
]);
|
|
533
|
+
input.keyEvent("Space", true, 0), input.keyEvent("Space", false, 1), input.keyEvent("KeyD", true, 2);
|
|
534
|
+
let pressedSeen = 0;
|
|
535
|
+
game.addSystem({ fixedUpdate: (g) => {
|
|
536
|
+
pressedSeen += g.input.justPressed(jump) ? 1 : 0;
|
|
537
|
+
} }), game.advance(2), expect(pressedSeen).toBe(1), expect(input.value(move)).toBe(1), input.gamepadAxisEvent(0, -0.9, 3), input.keyEvent("KeyD", false, 3), game.advance(1), expect(input.value(move)).toBeLessThan(-0.8), input.gamepadAxisEvent(0, 0, 4), input.virtualEvent("stick-x", 0.4, 4), game.advance(1), expect(input.value(move)).toBeCloseTo(0.4, 5), game.dispose();
|
|
538
|
+
}), it("recognises swipes with curl and taps", async () => {
|
|
539
|
+
let game = await make(), input = game.input;
|
|
540
|
+
input.pointerEvent("down", 3, 0.5, 0.9, 0), input.pointerEvent("move", 3, 0.6, 0.6, 80), input.pointerEvent("up", 3, 0.5, 0.3, 160);
|
|
541
|
+
let swipes = 0, curl = 0;
|
|
542
|
+
game.addSystem({
|
|
543
|
+
fixedUpdate: (g) => {
|
|
544
|
+
g.input.swipe.ready && (swipes++, curl = g.input.swipe.curl);
|
|
545
|
+
}
|
|
546
|
+
}), game.advance(3), expect(swipes).toBe(1), expect(Math.abs(curl)).toBeGreaterThan(0.1), input.pointerEvent("down", 4, 0.2, 0.2, 200), input.pointerEvent("up", 4, 0.2, 0.2, 260);
|
|
547
|
+
let taps = 0;
|
|
548
|
+
game.addSystem({ fixedUpdate: (g) => {
|
|
549
|
+
taps += g.input.tap.ready ? 1 : 0;
|
|
550
|
+
} }), game.advance(2), expect(taps).toBe(1), game.dispose();
|
|
551
|
+
});
|
|
552
|
+
}), describe("physics", () => {
|
|
553
|
+
it("rests dynamic bodies on the ground and responds to impulses", async () => {
|
|
554
|
+
let game = await make();
|
|
555
|
+
ground(game);
|
|
556
|
+
let ball = spawnAt2(game, 0, 3, 0);
|
|
557
|
+
game.physics.addBody(ball, { kind: "dynamic" }), game.physics.addCollider(ball, { shape: { kind: "ball", radius: 0.5 } }), game.advance(180);
|
|
558
|
+
let p = game.transforms.position(ball, new Vec3());
|
|
559
|
+
expect(p.y).toBeCloseTo(0.5, 1), game.physics.applyImpulse(ball, new Vec3(0, 0, -5)), game.advance(10);
|
|
560
|
+
let v = game.physics.linearVelocity(ball, new Vec3());
|
|
561
|
+
expect(v.z).toBeLessThan(-0.5), game.dispose();
|
|
562
|
+
}), it("casts rays with distances and surface normals", async () => {
|
|
563
|
+
let game = await make();
|
|
564
|
+
ground(game);
|
|
565
|
+
let box = spawnAt2(game, 0, 1, -5);
|
|
566
|
+
game.transforms.setRotationQuat(box, new Quat().setEuler(0, 0.5, 0)), game.physics.addCollider(box, { shape: { kind: "cuboid", halfExtents: [1, 1, 1] } });
|
|
567
|
+
let mesh = spawnAt2(game, 10, 0.5, 0);
|
|
568
|
+
game.physics.addCollider(mesh, {
|
|
569
|
+
shape: {
|
|
570
|
+
kind: "trimesh",
|
|
571
|
+
vertices: new Float32Array([-1, 0, -1, 1, 0, -1, 0, 0, 1]),
|
|
572
|
+
indices: new Uint32Array([0, 2, 1])
|
|
573
|
+
}
|
|
574
|
+
}), game.advance(1);
|
|
575
|
+
let hit = new RayHit(), ray = new RayQuery();
|
|
576
|
+
expect(game.physics.raycast(ray.set(0, 5, 0, 0, -1, 0, 10), hit)).toBe(true), expect(hit.distance).toBeCloseTo(5, 3), expect(hit.normalY).toBeCloseTo(1, 3), expect(game.physics.raycast(ray.set(0, 1, 5, 0, 0, -1, 20), hit)).toBe(true), expect(game.physics.entityOfCollider(hit.collider)).toBe(box), expect(hit.normalZ).toBeGreaterThan(0.5), expect(game.physics.raycast(ray.set(10, 3, 0, 0, -1, 0, 10), hit)).toBe(true), expect(game.physics.entityOfCollider(hit.collider)).toBe(mesh), expect(hit.normalY).toBeCloseTo(1, 2), expect(game.physics.raycast(ray.set(0, 5, 0, 0, 1, 0, 10), hit)).toBe(false), game.dispose();
|
|
577
|
+
}), it("reports sensor and contact events", async () => {
|
|
578
|
+
let game = await make();
|
|
579
|
+
ground(game);
|
|
580
|
+
let zone = spawnAt2(game, 0, 1, 0);
|
|
581
|
+
game.physics.addCollider(zone, { shape: { kind: "cuboid", halfExtents: [1, 1, 1] }, sensor: true });
|
|
582
|
+
let ball = spawnAt2(game, 0, 4, 0);
|
|
583
|
+
game.physics.addBody(ball, { kind: "dynamic" }), game.physics.addCollider(ball, { shape: { kind: "ball", radius: 0.25 }, events: true });
|
|
584
|
+
let entered = false;
|
|
585
|
+
game.addSystem({
|
|
586
|
+
fixedUpdate: (g) => {
|
|
587
|
+
for (let i = 0; i < g.physics.contactCount(); i++) {
|
|
588
|
+
let a = g.physics.contactEntityA(i), b = g.physics.contactEntityB(i);
|
|
589
|
+
g.physics.contactStarted(i) && (a === zone || b === zone) && (entered = true);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}), game.advance(120), expect(entered).toBe(true), game.dispose();
|
|
593
|
+
}), it("constrains bodies with joints", async () => {
|
|
594
|
+
let game = await make(), anchor = spawnAt2(game, 0, 5, 0);
|
|
595
|
+
game.physics.addBody(anchor, { kind: "fixed" }), game.physics.addCollider(anchor, { shape: { kind: "ball", radius: 0.1 } });
|
|
596
|
+
let bob = spawnAt2(game, 2, 5, 0);
|
|
597
|
+
game.physics.addBody(bob, { kind: "dynamic" }), game.physics.addCollider(bob, { shape: { kind: "ball", radius: 0.2 } }), game.physics.joint(anchor, bob, { kind: "spherical", anchorA: [0, 0, 0], anchorB: [-2, 0, 0] }), game.advance(90);
|
|
598
|
+
let p = game.transforms.position(bob, new Vec3());
|
|
599
|
+
expect(Math.hypot(p.x, p.y - 5, p.z)).toBeCloseTo(2, 1), expect(p.y).toBeLessThan(5), game.dispose();
|
|
600
|
+
}), it("walks a character controller, stays grounded and slides along walls", async () => {
|
|
601
|
+
let game = await make();
|
|
602
|
+
ground(game);
|
|
603
|
+
let wall = spawnAt2(game, 2, 1, 0);
|
|
604
|
+
game.physics.addCollider(wall, { shape: { kind: "cuboid", halfExtents: [0.1, 1, 5] } });
|
|
605
|
+
let hero = spawnAt2(game, 0, 0.9, 0);
|
|
606
|
+
game.physics.addBody(hero, { kind: "kinematic" });
|
|
607
|
+
let collider = game.physics.addCollider(hero, { shape: { kind: "capsule", halfHeight: 0.5, radius: 0.3 } }), controller = game.physics.createCharacterController({ snapToGround: 0.3, maxSlopeClimbDegrees: 45 }), move = new CharacterMove(), desired = new Vec3(), grounded = false;
|
|
608
|
+
game.advance(1), game.addSystem({
|
|
609
|
+
fixedUpdate: (g) => {
|
|
610
|
+
let dt = g.stepSeconds;
|
|
611
|
+
g.physics.moveCharacter(controller, collider, desired.set(2 * dt, -9.81 * dt, dt), move), grounded = move.grounded;
|
|
612
|
+
}
|
|
613
|
+
}), game.advance(120);
|
|
614
|
+
let p = game.transforms.position(hero, new Vec3());
|
|
615
|
+
expect(p.x).toBeGreaterThan(1.4), expect(p.x).toBeLessThan(1.65), expect(p.z).toBeGreaterThan(1.5), expect(p.y).toBeCloseTo(0.81, 1), expect(grounded).toBe(true), game.dispose();
|
|
616
|
+
});
|
|
617
|
+
}), describe("animation", () => {
|
|
618
|
+
it("tweens transforms and values with easing, delay and repeat", async () => {
|
|
619
|
+
let game = await make(), e = spawnAt2(game, 0, 0, 0), anim = game.animation, t = anim.tween(e, TweenTarget.PositionY, 3, 0.5, Ease.OutQuad), slot = anim.createValue(0);
|
|
620
|
+
anim.tweenValue(slot, 1, 0.25, Ease.Linear, 0.25);
|
|
621
|
+
let loop = anim.createValue(0);
|
|
622
|
+
anim.tweenValue(loop, 1, 0.5, Ease.Linear, 0, Repeat.PingPong), game.advance(15), expect(anim.value(slot)).toBeCloseTo(0, 5), game.advance(15), expect(game.transforms.position(e, new Vec3()).y).toBeCloseTo(3, 4), expect(anim.isActive(t)).toBe(false), expect(anim.value(slot)).toBeCloseTo(1, 4), game.advance(15), expect(anim.value(loop)).toBeCloseTo(0.5, 1), game.dispose();
|
|
623
|
+
}), it("samples keyframe clips including rotation", async () => {
|
|
624
|
+
let { Clip } = await import('../animation-6DICNWUP.mjs'), game = await make(), e = spawnAt2(game, 0, 0, 0), half = Math.SQRT1_2, clip = new Clip({
|
|
625
|
+
duration: 1,
|
|
626
|
+
tracks: [
|
|
627
|
+
{ target: TweenTarget.PositionX, times: new Float32Array([0, 1]), values: new Float32Array([0, 10]) },
|
|
628
|
+
{ target: TweenTarget.Rotation, times: new Float32Array([0, 1]), values: new Float32Array([0, 0, 0, 1, 0, half, 0, half]) }
|
|
629
|
+
]
|
|
630
|
+
});
|
|
631
|
+
game.animation.playClip(e, clip), game.advance(30), expect(game.transforms.position(e, new Vec3()).x).toBeCloseTo(5, 0), game.advance(60);
|
|
632
|
+
let q = game.transforms.rotation(e, new Quat());
|
|
633
|
+
expect(q.y).toBeCloseTo(half, 3), game.dispose();
|
|
634
|
+
});
|
|
635
|
+
}), describe("audio and game UI", () => {
|
|
636
|
+
it("plays voices on buses and tracks spatial voices", async () => {
|
|
637
|
+
let game = await make(), sound = await game.audio.load({ bytes: new ArrayBuffer(8) }), voice = game.audio.play(sound, game.audio.effects, 0.5, 1, true);
|
|
638
|
+
expect(game.audio.isPlaying(voice)).toBe(true), game.audio.setBusGain(game.audio.music, 0.2), game.audio.duck(game.audio.music, 0.5, 0.1, 0.05);
|
|
639
|
+
let emitter = spawnAt2(game, 3, 0, 0), spatial = game.audio.playAt(emitter, sound, game.audio.effects, 1, 1, true);
|
|
640
|
+
game.advance(10), game.audio.stop(voice), expect(game.audio.isPlaying(voice)).toBe(false), expect(game.audio.isPlaying(spatial)).toBe(true), game.dispose();
|
|
641
|
+
}), it("batches HUD updates to the native host once per frame", async () => {
|
|
642
|
+
let host = new RecordingUiHost(), game = await make(1, host), score = game.ui.hudNumber("score"), label = game.ui.hudText("label", "Go");
|
|
643
|
+
game.advance(1), expect(host.layout.map((s) => s.name)).toEqual(["score", "label"]);
|
|
644
|
+
let before = host.publishes;
|
|
645
|
+
game.ui.setNumber(score, 1), game.ui.setNumber(score, 2), game.ui.setText(label, "Goal"), game.advance(1), expect(host.publishes).toBe(before + 1), expect(host.lastDirty).toBe(2), game.advance(1), expect(host.publishes).toBe(before + 1);
|
|
646
|
+
let restart = game.ui.defineAction("restart");
|
|
647
|
+
expect(game.ui.dispatch("restart")).toBe(true), expect(game.ui.pollAction()).toBe(restart), game.ui.show("pause", { score: 2 }), game.ui.haptic("success"), expect(host.panels).toEqual(["pause"]), expect(host.haptics).toEqual(["success"]), game.dispose();
|
|
648
|
+
});
|
|
649
|
+
}), describe("networking", () => {
|
|
650
|
+
it("bit-packs messages and decodes them in place", async () => {
|
|
651
|
+
let game = await make(), Move = game.net.defineMessage("move", {
|
|
652
|
+
seq: "u16",
|
|
653
|
+
dx: quantized(-1, 1, 10),
|
|
654
|
+
jump: "bool",
|
|
655
|
+
yaw: "f32",
|
|
656
|
+
tick: "u32",
|
|
657
|
+
delta: "i16"
|
|
658
|
+
});
|
|
659
|
+
expect(Move.bytes).toBe(Math.ceil(115 / 8));
|
|
660
|
+
let writer = new MessageWriter();
|
|
661
|
+
writer.begin(Move).set(Move.field("seq"), 513).set(Move.field("dx"), -0.25).setBool(Move.field("jump"), true).set(Move.field("yaw"), 1.5).set(Move.field("tick"), 4e9).set(Move.field("delta"), -300);
|
|
662
|
+
let [a, b] = LoopbackTransport.pair();
|
|
663
|
+
game.net.connect(a);
|
|
664
|
+
let peer = await make();
|
|
665
|
+
peer.net.defineMessage("move", { seq: "u16", dx: quantized(-1, 1, 10), jump: "bool", yaw: "f32", tick: "u32", delta: "i16" }), peer.net.connect(b);
|
|
666
|
+
let inbound = peer.net.channel("inputs", "reliable");
|
|
667
|
+
game.net.channel("inputs", "reliable").send(writer);
|
|
668
|
+
let reader = peer.net.reader();
|
|
669
|
+
expect(inbound.receive(reader)).toBe(true), expect(reader.get(Move.field("seq"))).toBe(513), expect(reader.get(Move.field("dx"))).toBeCloseTo(-0.25, 2), expect(reader.getBool(Move.field("jump"))).toBe(true), expect(reader.get(Move.field("yaw"))).toBeCloseTo(1.5, 6), expect(reader.get(Move.field("tick"))).toBe(4e9), expect(reader.get(Move.field("delta"))).toBe(-300), expect(inbound.receive(reader)).toBe(false), game.dispose(), peer.dispose();
|
|
670
|
+
}), it("keeps only the newest message on latest-value channels", async () => {
|
|
671
|
+
let game = await make(), Pos = game.net.defineMessage("pos", { x: "f32" }), [a, b] = LoopbackTransport.pair();
|
|
672
|
+
game.net.connect(a);
|
|
673
|
+
let peer = await make();
|
|
674
|
+
peer.net.defineMessage("pos", { x: "f32" }), peer.net.connect(b);
|
|
675
|
+
let inbound = peer.net.channel("state", "latest"), outbound = game.net.channel("state", "latest"), writer = new MessageWriter();
|
|
676
|
+
for (let i = 0; i < 5; i++) outbound.send(writer.begin(Pos).set(Pos.field("x"), i));
|
|
677
|
+
let reader = peer.net.reader();
|
|
678
|
+
expect(inbound.receive(reader)).toBe(true), expect(reader.get(Pos.field("x"))).toBe(4), expect(inbound.receive(reader)).toBe(false), game.dispose(), peer.dispose();
|
|
679
|
+
}), it("interpolates remote snapshots", () => {
|
|
680
|
+
let buffer = new SnapshotBuffer(2, 8), identity = new Float32Array([0, 0, 0, 1]);
|
|
681
|
+
buffer.push(1, 10, new Float32Array([0, 0, 0]), 0, identity, 0), buffer.push(1, 12, new Float32Array([4, 0, 0]), 0, identity, 0);
|
|
682
|
+
let p = new Float32Array(3), r = new Float32Array(4);
|
|
683
|
+
expect(buffer.sample(1, 11, p, 0, r, 0)).toBe(true), expect(p[0]).toBeCloseTo(2, 5), expect(buffer.sample(0, 11, p, 0, r, 0)).toBe(false);
|
|
684
|
+
});
|
|
685
|
+
}), describe("determinism", () => {
|
|
686
|
+
let simulate = async (seed) => {
|
|
687
|
+
let game = await make(seed);
|
|
688
|
+
ground(game);
|
|
689
|
+
let bodies = [];
|
|
690
|
+
for (let i = 0; i < 12; i++) {
|
|
691
|
+
let e = spawnAt2(game, game.random.range(-3, 3), 2 + i * 0.6, game.random.range(-3, 3));
|
|
692
|
+
game.physics.addBody(e, { kind: "dynamic" }), game.physics.addCollider(e, { shape: i % 2 === 0 ? { kind: "ball", radius: 0.3 } : { kind: "cuboid", halfExtents: [0.3, 0.3, 0.3] } }), bodies.push(e);
|
|
693
|
+
}
|
|
694
|
+
let kick = new Vec3();
|
|
695
|
+
game.addSystem({
|
|
696
|
+
fixedUpdate: (g) => {
|
|
697
|
+
if (g.tick % 20 !== 0) return;
|
|
698
|
+
let target = bodies[g.random.int(0, bodies.length - 1)];
|
|
699
|
+
g.physics.applyImpulse(target, kick.set(g.random.range(-1, 1), g.random.range(0, 2), g.random.range(-1, 1)));
|
|
700
|
+
}
|
|
701
|
+
}), game.advance(240);
|
|
702
|
+
let out = Float32Array.from(game.transforms.store.fields.position.subarray(0, game.transforms.store.count * 3));
|
|
703
|
+
return game.dispose(), out;
|
|
704
|
+
};
|
|
705
|
+
it("reproduces identical state from the same seed", async () => {
|
|
706
|
+
let a = await simulate(42), b = await simulate(42);
|
|
707
|
+
expect(sameBits(a, b)).toBe(true);
|
|
708
|
+
}), it("diverges for a different seed", async () => {
|
|
709
|
+
let a = await simulate(42), c = await simulate(43);
|
|
710
|
+
expect(sameBits(a, c)).toBe(false);
|
|
711
|
+
});
|
|
712
|
+
}), describe("frame-loop allocation", () => {
|
|
713
|
+
let measure = (vendorQueries) => options.allocation ? options.allocation(vendorQueries) : measureSustainedAllocation(
|
|
714
|
+
options.createBackend,
|
|
715
|
+
options.heap,
|
|
716
|
+
vendorQueries,
|
|
717
|
+
options.sustainedFrames,
|
|
718
|
+
(true)
|
|
719
|
+
), withClock = (run2) => options.hostClock === "frozen" ? withFrozenHostClock(run2) : run2();
|
|
720
|
+
it("allocates nothing and never collects garbage across a sustained headless run", async () => {
|
|
721
|
+
let result = await withClock(() => measure(false));
|
|
722
|
+
if (!result) return;
|
|
723
|
+
let [report, longer] = result;
|
|
724
|
+
options.report?.("sustained", report), options.report?.("sustained-2x", longer), expect(report.collections).toBe(0), expect(longer.collections).toBe(0), expect(report.bytesPerFrame).toBeLessThan((1)), expect(longer.bytesPerFrame).toBeLessThan((1));
|
|
725
|
+
}, 12e4), it("keeps physics queries and dynamic body readback within the backend's declared boundary budget", async () => {
|
|
726
|
+
let result = await withClock(() => measure(true));
|
|
727
|
+
if (!result) return;
|
|
728
|
+
let [report, longer] = result;
|
|
729
|
+
options.report?.("physics-queries", report), options.report?.("physics-queries-2x", longer);
|
|
730
|
+
let budget = options.physicsBoundaryBudget ?? NO_BOUNDARY_BUDGET, perFrame = (48) * budget.bytesPerAwakeBodyStep + budget.bytesPerRaycast + budget.bytesPerCharacterMove;
|
|
731
|
+
expect(report.bytesPerFrame).toBeLessThanOrEqual(perFrame + (1)), expect(longer.bytesPerFrame).toBeLessThanOrEqual(perFrame + (1)), perFrame === 0 && expect(longer.collections).toBe(0);
|
|
732
|
+
}, 12e4);
|
|
733
|
+
}), describe("hot-path microbenchmarks", () => {
|
|
734
|
+
it("keeps math, transform, query, codec and raycast calls within their nanosecond budgets", async (context) => {
|
|
735
|
+
let results = await (options.microbench ?? (() => runMicrobench(options.createBackend)))();
|
|
736
|
+
options.reportMicrobench?.(results);
|
|
737
|
+
let busy = await busyMachine();
|
|
738
|
+
busy !== null && (console.info(`${loadSkipNotice(busy)}: ${JSON.stringify(results, (_key, value) => typeof value == "number" ? Math.round(value * 10) / 10 : value)}`), context.skip());
|
|
739
|
+
for (let name of Object.keys(results)) {
|
|
740
|
+
let budget = options.microbenchBudgetsNs?.[name] ?? DEFAULT_MICROBENCH_BUDGETS_NS[name];
|
|
741
|
+
expect(results[name], `${name} ${results[name].toFixed(1)}ns`).toBeLessThan(budget);
|
|
742
|
+
}
|
|
743
|
+
}, (12e4));
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
var FROZEN_HOST_TIME = 0;
|
|
748
|
+
function frozenNow() {
|
|
749
|
+
return (0);
|
|
750
|
+
}
|
|
751
|
+
var NO_BOUNDARY_BUDGET = { bytesPerAwakeBodyStep: 0, bytesPerRaycast: 0, bytesPerCharacterMove: 0 };
|
|
752
|
+
function halfway() {
|
|
753
|
+
let time2 = new FrameTime();
|
|
754
|
+
return time2.values[FRAME_ALPHA_SLOT] = 0.5, time2;
|
|
755
|
+
}
|
|
756
|
+
function sameBits(a, b) {
|
|
757
|
+
if (a.length !== b.length) return false;
|
|
758
|
+
let x = new Uint32Array(a.buffer, a.byteOffset, a.length), y = new Uint32Array(b.buffer, b.byteOffset, b.length);
|
|
759
|
+
for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
var GLB_MAGIC = 1179937895, GLB_VERSION = 2, GLB_CHUNK_JSON = 1313821514, GLB_CHUNK_BIN = 5130562, GLB_HEADER_BYTES = 12, GLB_CHUNK_HEADER_BYTES = 8, GLB_ALIGN = 4, SPACE = 32, RIG_HIPS = "mixamorig_Hips", RIG_WALK_SPEED = 2;
|
|
763
|
+
function glbFrom(json, bin) {
|
|
764
|
+
let text = new TextEncoder().encode(JSON.stringify(json)), jsonLength = Math.ceil(text.length / (4)) * (4), binLength = Math.ceil(bin.length / (4)) * (4), total = (12) + (8) * 2 + jsonLength + binLength, out = new ArrayBuffer(total), view = new DataView(out), bytes = new Uint8Array(out);
|
|
765
|
+
view.setUint32(0, (1179937895), true), view.setUint32(4, (2), true), view.setUint32(8, total, true);
|
|
766
|
+
let o = (12);
|
|
767
|
+
return view.setUint32(o, jsonLength, true), view.setUint32(o + 4, (1313821514), true), o += (8), bytes.fill((32), o, o + jsonLength), bytes.set(text, o), o += jsonLength, view.setUint32(o, binLength, true), view.setUint32(o + 4, (5130562), true), o += (8), bytes.set(bin, o), out;
|
|
768
|
+
}
|
|
769
|
+
function buildRigGlb() {
|
|
770
|
+
let streams = [
|
|
771
|
+
new Float32Array([0, 1]),
|
|
772
|
+
new Float32Array([0, 2]),
|
|
773
|
+
new Float32Array([0, 0, 0, (2), 0, 0]),
|
|
774
|
+
new Float32Array([0, 0, 0, (2) * 2, 0, 0]),
|
|
775
|
+
new Float32Array([0, 0, 0, 0, 0, 0]),
|
|
776
|
+
new Float32Array([4, 0, 0, 4, 0, 0]),
|
|
777
|
+
new Float32Array([0, 1, 2]),
|
|
778
|
+
new Float32Array([0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, -1, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 0])
|
|
779
|
+
], bin = new Uint8Array(streams.reduce((sum, s) => sum + s.byteLength, 0)), bufferViews = [], offset = 0;
|
|
780
|
+
for (let stream of streams)
|
|
781
|
+
bin.set(new Uint8Array(stream.buffer), offset), bufferViews.push({ buffer: 0, byteOffset: offset, byteLength: stream.byteLength }), offset += stream.byteLength;
|
|
782
|
+
let clip = (name, input, output, interpolation = "LINEAR") => ({
|
|
783
|
+
name,
|
|
784
|
+
samplers: [{ input, output, interpolation }],
|
|
785
|
+
channels: [{ sampler: 0, target: { node: 0, path: "translation" } }]
|
|
786
|
+
});
|
|
787
|
+
return glbFrom(
|
|
788
|
+
{
|
|
789
|
+
asset: { version: "2.0" },
|
|
790
|
+
scene: 0,
|
|
791
|
+
scenes: [{ nodes: [0] }],
|
|
792
|
+
nodes: [
|
|
793
|
+
{ name: RIG_HIPS, children: [1] },
|
|
794
|
+
{ name: "mixamorig_Spine", translation: [0, 0.2, 0], children: [2] },
|
|
795
|
+
{ name: "mixamorig_Spine1", translation: [0, 0.2, 0] }
|
|
796
|
+
],
|
|
797
|
+
buffers: [{ byteLength: bin.byteLength }],
|
|
798
|
+
bufferViews,
|
|
799
|
+
accessors: [
|
|
800
|
+
{ bufferView: 0, componentType: 5126, count: 2, type: "SCALAR", min: [0], max: [1] },
|
|
801
|
+
{ bufferView: 1, componentType: 5126, count: 2, type: "SCALAR", min: [0], max: [2] },
|
|
802
|
+
{ bufferView: 2, componentType: 5126, count: 2, type: "VEC3" },
|
|
803
|
+
{ bufferView: 3, componentType: 5126, count: 2, type: "VEC3" },
|
|
804
|
+
{ bufferView: 4, componentType: 5126, count: 2, type: "VEC3" },
|
|
805
|
+
{ bufferView: 5, componentType: 5126, count: 2, type: "VEC3" },
|
|
806
|
+
{ bufferView: 6, componentType: 5126, count: 3, type: "SCALAR", min: [0], max: [2] },
|
|
807
|
+
{ bufferView: 7, componentType: 5126, count: 9, type: "VEC3" }
|
|
808
|
+
],
|
|
809
|
+
animations: [
|
|
810
|
+
clip("walk", 0, 2),
|
|
811
|
+
clip("run", 1, 3),
|
|
812
|
+
clip("low", 0, 4),
|
|
813
|
+
clip("high", 0, 5),
|
|
814
|
+
clip("curve", 6, 7, "CUBICSPLINE")
|
|
815
|
+
]
|
|
816
|
+
},
|
|
817
|
+
bin
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
function buildTriangleGlb() {
|
|
821
|
+
let positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), times = new Float32Array([0, 1]), rotations = new Float32Array([0, 0, 0, 1, 0, 0.7071068, 0, 0.7071068]), bin = new Uint8Array(positions.byteLength + times.byteLength + rotations.byteLength);
|
|
822
|
+
bin.set(new Uint8Array(positions.buffer), 0), bin.set(new Uint8Array(times.buffer), positions.byteLength), bin.set(new Uint8Array(rotations.buffer), positions.byteLength + times.byteLength);
|
|
823
|
+
let json = new TextEncoder().encode(triangleJson(bin.length, positions.byteLength, times.byteLength, rotations.byteLength)), jsonLength = Math.ceil(json.length / (4)) * (4), binLength = Math.ceil(bin.length / (4)) * (4), total = (12) + (8) * 2 + jsonLength + binLength, out = new ArrayBuffer(total), view = new DataView(out), bytes = new Uint8Array(out);
|
|
824
|
+
view.setUint32(0, (1179937895), true), view.setUint32(4, (2), true), view.setUint32(8, total, true);
|
|
825
|
+
let o = (12);
|
|
826
|
+
return view.setUint32(o, jsonLength, true), view.setUint32(o + 4, (1313821514), true), o += (8), bytes.fill((32), o, o + jsonLength), bytes.set(json, o), o += jsonLength, view.setUint32(o, binLength, true), view.setUint32(o + 4, (5130562), true), o += (8), bytes.set(bin, o), out;
|
|
827
|
+
}
|
|
828
|
+
function triangleJson(byteLength, positionBytes, timeBytes, rotationBytes) {
|
|
829
|
+
return JSON.stringify({
|
|
830
|
+
asset: { version: "2.0" },
|
|
831
|
+
scene: 0,
|
|
832
|
+
scenes: [{ nodes: [0] }],
|
|
833
|
+
nodes: [{ name: "Tri", mesh: 0 }],
|
|
834
|
+
meshes: [{ primitives: [{ attributes: { POSITION: 0 }, material: 0 }] }],
|
|
835
|
+
materials: [{ name: "Grass", pbrMetallicRoughness: { baseColorFactor: [0, 1, 0, 1], roughnessFactor: 0.5 } }],
|
|
836
|
+
buffers: [{ byteLength }],
|
|
837
|
+
bufferViews: [
|
|
838
|
+
{ buffer: 0, byteOffset: 0, byteLength: positionBytes },
|
|
839
|
+
{ buffer: 0, byteOffset: positionBytes, byteLength: timeBytes },
|
|
840
|
+
{ buffer: 0, byteOffset: positionBytes + timeBytes, byteLength: rotationBytes }
|
|
841
|
+
],
|
|
842
|
+
accessors: [
|
|
843
|
+
{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3", min: [0, 0, 0], max: [1, 1, 0] },
|
|
844
|
+
{ bufferView: 1, componentType: 5126, count: 2, type: "SCALAR", min: [0], max: [1] },
|
|
845
|
+
{ bufferView: 2, componentType: 5126, count: 2, type: "VEC4" }
|
|
846
|
+
],
|
|
847
|
+
animations: [
|
|
848
|
+
{
|
|
849
|
+
name: "spin",
|
|
850
|
+
samplers: [{ input: 1, output: 2, interpolation: "LINEAR" }],
|
|
851
|
+
channels: [{ sampler: 0, target: { node: 0, path: "rotation" } }]
|
|
852
|
+
}
|
|
853
|
+
]
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
var LOCK_DIR = join(tmpdir(), "cimplify-engine-measurement.lock"), OWNER_FILE = join(LOCK_DIR, "owner"), POLL_MS = 5, NO_SIGNAL = 0, pause = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
857
|
+
function alive(pid) {
|
|
858
|
+
try {
|
|
859
|
+
return process.kill(pid, (0)), !0;
|
|
860
|
+
} catch {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function owner() {
|
|
865
|
+
try {
|
|
866
|
+
return Number(readFileSync(OWNER_FILE, "utf8"));
|
|
867
|
+
} catch {
|
|
868
|
+
return Number.NaN;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function acquire() {
|
|
872
|
+
for (; ; )
|
|
873
|
+
try {
|
|
874
|
+
mkdirSync(LOCK_DIR), writeFileSync(OWNER_FILE, String(process.pid));
|
|
875
|
+
return;
|
|
876
|
+
} catch {
|
|
877
|
+
let pid = owner();
|
|
878
|
+
Number.isInteger(pid) && !alive(pid) ? rmSync(LOCK_DIR, { recursive: true, force: true }) : Atomics.wait(pause, 0, 0, (5));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function exclusiveMeasurement(run2) {
|
|
882
|
+
acquire();
|
|
883
|
+
try {
|
|
884
|
+
return run2();
|
|
885
|
+
} finally {
|
|
886
|
+
rmSync(LOCK_DIR, { recursive: true, force: true });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/game/conformance/isolated.ts
|
|
891
|
+
var ISOLATED_RESULT_PREFIX = "isolated-result:", ISOLATED_TIMEOUT_MS = 6e5, CHILD_BUFFER_BYTES = 1 << 24, ISOLATION_FLAGS = ["--expose-gc", "--no-concurrent-recompilation", "--no-concurrent-osr", "--no-concurrent-sparkplug"], NUMERIC_VAR = /^var ([A-Za-z_$][\w$]*) = (-?(?:0x[0-9a-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?));$/gm, REGEX_SPECIAL = /[$]/g, bundles = /* @__PURE__ */ new Map();
|
|
892
|
+
function writes(code, name) {
|
|
893
|
+
let id = name.replace(REGEX_SPECIAL, "\\$&"), assigned = new RegExp(`(?<![\\w$.])${id}\\s*(?:[-+*/%&|^]|\\*\\*|<<|>>>?|\\?\\?|&&|\\|\\|)?=(?!=)`, "g"), stepped = new RegExp(`(?:\\+\\+|--)\\s*${id}(?![\\w$])|(?<![\\w$.])${id}\\s*(?:\\+\\+|--)`, "g");
|
|
894
|
+
return (code.match(assigned)?.length ?? 0) + (code.match(stepped)?.length ?? 0);
|
|
895
|
+
}
|
|
896
|
+
function restoreConstants(code) {
|
|
897
|
+
return code.replace(NUMERIC_VAR, (line, name, value) => writes(code, name) === 1 ? `const ${name} = ${value};` : line);
|
|
898
|
+
}
|
|
899
|
+
function bundle(key, write) {
|
|
900
|
+
let known = bundles.get(key);
|
|
901
|
+
if (known !== void 0) return known;
|
|
902
|
+
let dir = mkdtempSync(join(tmpdir(), "cimplify-isolated-")), entry = write(dir), outfile = join(dir, "isolated.mjs");
|
|
903
|
+
return execFileSync("bun", ["build", entry, "--target=node", "--format=esm", "--outfile", outfile], { stdio: "pipe" }), writeFileSync(outfile, restoreConstants(readFileSync(outfile, "utf8"))), bundles.set(key, outfile), outfile;
|
|
904
|
+
}
|
|
905
|
+
function run(script, args) {
|
|
906
|
+
let output = exclusiveMeasurement(
|
|
907
|
+
() => execFileSync("node", [...ISOLATION_FLAGS, script, ...args], { encoding: "utf8", maxBuffer: CHILD_BUFFER_BYTES, timeout: (6e5) })
|
|
908
|
+
), line = output.split(`
|
|
909
|
+
`).find((l) => l.startsWith(ISOLATED_RESULT_PREFIX));
|
|
910
|
+
if (!line) throw new Error(`the isolated measurement produced no result: ${output}`);
|
|
911
|
+
return JSON.parse(line.slice(ISOLATED_RESULT_PREFIX.length));
|
|
912
|
+
}
|
|
913
|
+
function isolatedModule(source, args) {
|
|
914
|
+
return run(
|
|
915
|
+
bundle(source, (dir) => {
|
|
916
|
+
let entry = join(dir, "entry.ts");
|
|
917
|
+
return writeFileSync(entry, source), entry;
|
|
918
|
+
}),
|
|
919
|
+
args
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/game/conformance/template-suite.ts
|
|
924
|
+
var FILE_PROTOCOL = "file:";
|
|
925
|
+
function sourcePath(relative, base) {
|
|
926
|
+
let url = new URL(relative, base), pathname = decodeURIComponent(url.pathname), host = globalThis.process;
|
|
927
|
+
return url.protocol === FILE_PROTOCOL || host === void 0 ? pathname : `${host.cwd()}${pathname}`;
|
|
928
|
+
}
|
|
929
|
+
var TEMPLATE_ALLOCATION_MODULE = sourcePath("./template-allocation.ts", import.meta.url); sourcePath("../backends/prepared.ts", import.meta.url);
|
|
930
|
+
function isolatedSource(isolated) {
|
|
931
|
+
return [
|
|
932
|
+
`import { runTemplateAllocationChild } from ${JSON.stringify(TEMPLATE_ALLOCATION_MODULE)};`,
|
|
933
|
+
`import { ${isolated.exportName} as template } from ${JSON.stringify(isolated.module)};`,
|
|
934
|
+
`import { ${isolated.backendExport} as createBackend } from ${JSON.stringify(isolated.backendModule)};`,
|
|
935
|
+
"await runTemplateAllocationChild(template, createBackend);",
|
|
936
|
+
""
|
|
937
|
+
].join(`
|
|
938
|
+
`);
|
|
939
|
+
}
|
|
940
|
+
var FS_MODULE = "node:fs", UTF8 = "utf8", DEFAULT_SEED = 11, DEFAULT_WARMUP_FRAMES = 3e4, DEFAULT_SUSTAINED_FRAMES2 = 6e3, RESIDUE_BYTES_PER_FRAME2 = 1, SUSTAINED_TIMEOUT_MS = 12e4;
|
|
941
|
+
function defineTemplateConformance(options) {
|
|
942
|
+
let template = options.template, seed = options.seed ?? (11), play = async (frames, runSeed) => {
|
|
943
|
+
let h = await createTemplateHarness(template, options.createBackend(), runSeed);
|
|
944
|
+
for (let i = 0; i < frames; i++) h.frame(i);
|
|
945
|
+
let result = [template.progress(h.scene), template.digest(h.scene, h.game)];
|
|
946
|
+
return h.game.dispose(), result;
|
|
947
|
+
};
|
|
948
|
+
describe(`genre template conformance: ${template.id}`, () => {
|
|
949
|
+
it("uses only the game API", async () => {
|
|
950
|
+
let fs = await import(
|
|
951
|
+
/* @vite-ignore */
|
|
952
|
+
FS_MODULE
|
|
953
|
+
), root = options.sourceRoot ?? "", files = template.sources.map((path) => ({ path, source: fs.readFileSync(root + path, UTF8) })), result = checkGameSources({ profile: "game_api" }, files);
|
|
954
|
+
expect(result.violations).toEqual([]);
|
|
955
|
+
}), it("passes the kit publication gates", () => {
|
|
956
|
+
let failed = kitPublicationGates(template.economy).filter((g) => !g.passed);
|
|
957
|
+
expect(failed).toEqual([]);
|
|
958
|
+
}), it("reaches its progress condition under the playtest bot", async () => {
|
|
959
|
+
let [progress] = await play(options.playtestFrames, seed);
|
|
960
|
+
expect(progress).toBeGreaterThanOrEqual(options.progressTarget);
|
|
961
|
+
}), it("replays identically from the same seed and inputs", async () => {
|
|
962
|
+
let a = await play(options.playtestFrames, seed), b = await play(options.playtestFrames, seed);
|
|
963
|
+
expect(b).toEqual(a);
|
|
964
|
+
}), it(
|
|
965
|
+
"allocates nothing and never collects garbage on the frame path across a sustained run",
|
|
966
|
+
async () => {
|
|
967
|
+
let isolated = options.isolated;
|
|
968
|
+
if (isolated !== void 0) {
|
|
969
|
+
let warmup = options.warmupFrames ?? (3e4), frames = options.sustainedFrames ?? (6e3), boundary = options.boundaryBytesPerFrame ?? 0, budget = boundary + (1), { report, sites } = isolatedModule(isolatedSource(isolated), [String(seed), String(warmup), String(frames), String(budget)]);
|
|
970
|
+
options.report?.(template.id, report);
|
|
971
|
+
let busy = await busyMachine();
|
|
972
|
+
busy !== null && console.info(`${template.id}: collection count ${loadSkipNotice(busy)}`), boundary === 0 && busy === null && expect(report.collections).toBe(0), expect(report.bytesPerFrame, `allocation sites:${sites}`).toBeLessThan(budget);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
await withFrozenHostClock(async () => {
|
|
976
|
+
let meter = await options.heap();
|
|
977
|
+
if (!meter) return;
|
|
978
|
+
let h = await createTemplateHarness(template, options.createBackend(), seed), warmup = options.warmupFrames ?? (3e4);
|
|
979
|
+
await warmUp(warmup, (i) => h.frame(i));
|
|
980
|
+
let frames = options.sustainedFrames ?? (6e3), report = await measureSteadyAllocations(meter, frames, (i) => h.frame(warmup + i));
|
|
981
|
+
meter.dispose(), options.report?.(template.id, report);
|
|
982
|
+
let boundary = options.boundaryBytesPerFrame ?? 0, budget = boundary + (1), sites = "";
|
|
983
|
+
report.bytesPerFrame >= budget && (sites = (await profileAllocationSites(frames, (i) => h.frame(warmup + frames + i)) ?? []).map((s) => `
|
|
984
|
+
${s.bytes} B ${s.stack}`).join("")), h.game.dispose();
|
|
985
|
+
let busy = await busyMachine();
|
|
986
|
+
busy !== null && console.info(`${template.id}: collection count ${loadSkipNotice(busy)}`), boundary === 0 && busy === null && expect(report.collections).toBe(0), expect(report.bytesPerFrame, `allocation sites:${sites}`).toBeLessThan(budget);
|
|
987
|
+
});
|
|
988
|
+
},
|
|
989
|
+
options.isolated === void 0 ? (12e4) : (6e5)
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export { DEFAULT_MICROBENCH_BUDGETS_NS, busyMachine, createSustainedHarness, defineGameConformanceSuite, defineTemplateConformance, frameStamp, loadSkipNotice, measureSustainedAllocation, runMicrobench, withFrozenHostClock };
|