diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a0623fce --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +.claude/ +docs/ +generated/ +public/manifest.json +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/README.md b/README.md index 39de6f56..be1dbf04 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,18 @@ Click inside the map preview area to capture the mouse. -| Key | Action | -|-----|--------| -| W | Forward | -| A | Left | -| S | Backward | -| D | Right | -| Space | Up | -| Shift | Down | -| Esc | Release mouse | -| Left click | Next observer camera | -| △ Scroll/mouse wheel up | Increase speed | -| ▽ Scroll/mouse wheel down | Decrease speed | - +| Key | Action | +| ---------------------------------------- | -------------------- | +| W | Forward | +| A | Left | +| S | Backward | +| D | Right | +| Space | Up | +| Shift | Down | +| Esc | Release mouse | +| Left click | Next observer camera | +| △ Scroll/mouse wheel up | Increase speed | +| ▽ Scroll/mouse wheel down | Decrease speed | ## Development diff --git a/app/page.tsx b/app/page.tsx index cabf8e0d..160fb5df 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -23,7 +23,7 @@ function MapInspector() { // Initialize state from query params const [missionName, setMissionName] = useState( - searchParams.get("mission") || "TWL2_WoodyMyrk" + searchParams.get("mission") || "TWL2_WoodyMyrk", ); // Update query params when state changes diff --git a/app/style.css b/app/style.css index 0ca39d1c..ea3bade4 100644 --- a/app/style.css +++ b/app/style.css @@ -6,8 +6,18 @@ body { } html { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Oxygen, + Ubuntu, + Cantarell, + "Open Sans", + "Helvetica Neue", + sans-serif; font-size: 100%; } diff --git a/package-lock.json b/package-lock.json index 4709f863..56783c21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,9 +28,11 @@ "@types/three": "^0.180.0", "@types/unzipper": "^0.10.11", "peggy": "^5.0.6", + "prettier": "^3.7.1", "rimraf": "^6.0.1", "tsx": "^4.20.5", - "typescript": "5.9.2" + "typescript": "5.9.2", + "vitest": "^4.0.14" } }, "node_modules/@babel/runtime": { @@ -959,6 +961,13 @@ "node": ">=12" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@mediapipe/tasks-vision": { "version": "0.10.17", "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", @@ -1247,6 +1256,321 @@ "three": ">=0.144.0" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1288,12 +1612,37 @@ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", "license": "MIT" }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", @@ -1402,6 +1751,117 @@ "react": ">= 16.8.0" } }, + "node_modules/@vitest/expect": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz", + "integrity": "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.14.tgz", + "integrity": "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.14", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.14.tgz", + "integrity": "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.14.tgz", + "integrity": "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.14", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.14.tgz", + "integrity": "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.14.tgz", + "integrity": "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", + "integrity": "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.14", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webgpu/types": { "version": "0.1.64", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.64.tgz", @@ -1434,6 +1894,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1526,6 +1996,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1679,6 +2159,13 @@ "dev": true, "license": "MIT" }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -1721,6 +2208,44 @@ "@esbuild/win32-x64": "0.25.9" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -1979,6 +2504,16 @@ "three": ">=0.134.0" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/meshline": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", @@ -2106,6 +2641,17 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -2139,6 +2685,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/peggy": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/peggy/-/peggy-5.0.6.tgz", @@ -2163,6 +2716,20 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -2207,6 +2774,22 @@ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", "license": "ISC" }, + "node_modules/prettier": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.1.tgz", + "integrity": "sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2348,6 +2931,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2437,6 +3062,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2479,6 +3111,13 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/stats-gl": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz", @@ -2505,6 +3144,13 @@ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", "license": "MIT" }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2689,6 +3335,47 @@ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/troika-three-text": { "version": "0.52.4", "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", @@ -2731,6 +3418,7 @@ "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -2849,6 +3537,189 @@ "node": ">= 4" } }, + "node_modules/vite": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", + "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.14.tgz", + "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.14", + "@vitest/mocker": "4.0.14", + "@vitest/pretty-format": "4.0.14", + "@vitest/runner": "4.0.14", + "@vitest/snapshot": "4.0.14", + "@vitest/spy": "4.0.14", + "@vitest/utils": "4.0.14", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.14", + "@vitest/browser-preview": "4.0.14", + "@vitest/browser-webdriverio": "4.0.14", + "@vitest/ui": "4.0.14", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/webgl-constants": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", @@ -2875,6 +3746,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index e837c18b..115d2716 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,13 @@ "build": "next build && touch docs/.nojekyll", "clean": "rimraf .next", "deploy": "npm run build && git add -f docs && git commit -m \"Deploy\" && git push", + "format": "prettier --write .", "postbuild": "git checkout -- public/base", "prebuild": "npm run clean && git checkout -- docs && rimraf public/base && mv docs/base public/", - "start": "next dev --turbopack" + "start": "next dev --turbopack", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit" }, "dependencies": { "@react-three/drei": "^10.7.6", @@ -35,8 +39,10 @@ "@types/three": "^0.180.0", "@types/unzipper": "^0.10.11", "peggy": "^5.0.6", + "prettier": "^3.7.1", "rimraf": "^6.0.1", "tsx": "^4.20.5", - "typescript": "5.9.2" + "typescript": "5.9.2", + "vitest": "^4.0.14" } } diff --git a/scripts/convert-dif.ts b/scripts/convert-dif.ts index e1bf929a..a5b21d3a 100644 --- a/scripts/convert-dif.ts +++ b/scripts/convert-dif.ts @@ -43,7 +43,7 @@ async function run({ onlyNew }: { onlyNew: boolean }) { "--", // args after here go to the script ...inputFiles, ], - { stdio: "inherit" } + { stdio: "inherit" }, ); } diff --git a/scripts/convert-dts.ts b/scripts/convert-dts.ts index 0be09e65..2a02f3be 100644 --- a/scripts/convert-dts.ts +++ b/scripts/convert-dts.ts @@ -27,7 +27,7 @@ async function run() { // "--no-anims", // "--only-visible", ], - { stdio: "inherit" } + { stdio: "inherit" }, ); } } diff --git a/scripts/extract-assets.ts b/scripts/extract-assets.ts index d1419804..73f0f5bf 100644 --- a/scripts/extract-assets.ts +++ b/scripts/extract-assets.ts @@ -23,7 +23,7 @@ async function buildExtractedGameDataFolder() { archives.set(source, archive); } const entry = archive.files.find( - (entry) => normalizePath(entry.path) === filePath + (entry) => normalizePath(entry.path) === filePath, ); const inFile = `${inputBaseDir}/${source}:${filePath}`; if (!entry) { diff --git a/scripts/generate-manifest.ts b/scripts/generate-manifest.ts index 05472755..4b47de7b 100644 --- a/scripts/generate-manifest.ts +++ b/scripts/generate-manifest.ts @@ -24,7 +24,7 @@ async function walkDirectory( entry: Dirent; fullPath: string; }) => boolean | Promise; - } + }, ): Promise { const entries = await fs.readdir(dir, { withFileTypes: true }); @@ -102,12 +102,12 @@ async function buildManifest() { archiveDirs = orderBy( archiveDirs, [(fullPath) => path.basename(fullPath).toLowerCase()], - ["asc"] + ["asc"], ); for (const archivePath of archiveDirs) { const relativeArchivePath = normalizePath( - path.relative(`${baseDir}/@vl2`, archivePath) + path.relative(`${baseDir}/@vl2`, archivePath), ); await walkDirectory(archivePath, { onFile: ({ dir, entry, fullPath }) => { @@ -140,7 +140,7 @@ async function buildManifest() { .map((source) => ` ❗️ ${source}`) .join("") : "" - }` + }`, ); const resolvedPath = lastSource diff --git a/scripts/inspect-ifl.ts b/scripts/inspect-ifl.ts index ba242474..6980b85a 100644 --- a/scripts/inspect-ifl.ts +++ b/scripts/inspect-ifl.ts @@ -30,7 +30,7 @@ async function run() { .map((f) => f.match(/^textures\/skins\/(.+)\.ifl$/)) .filter(Boolean) .map((match) => match[1]) - .join("\n") + .join("\n"), ); return; } else if ( @@ -38,7 +38,7 @@ async function run() { (!values.name && !positionals[0]) ) { console.error( - "Must specify exactly one of --name (-n) or a positional filename." + "Must specify exactly one of --name (-n) or a positional filename.", ); return 1; } @@ -53,7 +53,7 @@ async function run() { inspect(parseImageFrameList(missionScript), { colors: true, depth: Infinity, - }) + }), ); } diff --git a/scripts/inspect-interior.ts b/scripts/inspect-interior.ts index 3d119220..3fd0ce67 100644 --- a/scripts/inspect-interior.ts +++ b/scripts/inspect-interior.ts @@ -6,12 +6,12 @@ const interiorFile = process.argv[2]; const interiorBuffer = fs.readFileSync(interiorFile); const interiorArrayBuffer = interiorBuffer.buffer.slice( interiorBuffer.byteOffset, - interiorBuffer.byteOffset + interiorBuffer.byteLength + interiorBuffer.byteOffset + interiorBuffer.byteLength, ); console.log( inspect(parseInteriorBuffer(interiorArrayBuffer), { colors: true, depth: Infinity, - }) + }), ); diff --git a/scripts/inspect-mission.ts b/scripts/inspect-mission.ts index 7d4b7ab1..099e95b1 100644 --- a/scripts/inspect-mission.ts +++ b/scripts/inspect-mission.ts @@ -30,7 +30,7 @@ async function run() { .map((f) => f.match(/^missions\/(.+)\.mis$/)) .filter(Boolean) .map((match) => match[1]) - .join("\n") + .join("\n"), ); return; } else if ( @@ -38,7 +38,7 @@ async function run() { (!values.name && !positionals[0]) ) { console.error( - "Must specify exactly one of --name (-n) or a positional filename." + "Must specify exactly one of --name (-n) or a positional filename.", ); return 1; } @@ -53,7 +53,7 @@ async function run() { inspect(parseMissionScript(missionScript), { colors: true, depth: Infinity, - }) + }), ); } diff --git a/scripts/inspect-terrain.ts b/scripts/inspect-terrain.ts index a4c0e15a..7416d97f 100644 --- a/scripts/inspect-terrain.ts +++ b/scripts/inspect-terrain.ts @@ -30,7 +30,7 @@ async function run() { .map((f) => f.match(/^terrains\/(.+)\.ter$/)) .filter(Boolean) .map((match) => match[1]) - .join("\n") + .join("\n"), ); return; } else if ( @@ -38,7 +38,7 @@ async function run() { (!values.name && !positionals[0]) ) { console.error( - "Must specify exactly one of --name (-n) or a positional filename." + "Must specify exactly one of --name (-n) or a positional filename.", ); return 1; } @@ -51,14 +51,14 @@ async function run() { const terrainBuffer = fs.readFileSync(terrainFile); const terrainArrayBuffer = terrainBuffer.buffer.slice( terrainBuffer.byteOffset, - terrainBuffer.byteOffset + terrainBuffer.byteLength + terrainBuffer.byteOffset + terrainBuffer.byteLength, ); console.log( inspect(parseTerrainBuffer(terrainArrayBuffer), { colors: true, depth: Infinity, - }) + }), ); } diff --git a/scripts/mission-properties.ts b/scripts/mission-properties.ts index 69a4eaa5..0b6a8682 100644 --- a/scripts/mission-properties.ts +++ b/scripts/mission-properties.ts @@ -62,7 +62,7 @@ async function run({ console.log(property.value); } else { console.log( - `${baseName} > ${consoleObject.className} > ${property.target.name} = ${property.value}` + `${baseName} > ${consoleObject.className} > ${property.target.name} = ${property.value}`, ); } } diff --git a/src/components/AudioContext.tsx b/src/components/AudioContext.tsx index aa50c678..9f9f1a8f 100644 --- a/src/components/AudioContext.tsx +++ b/src/components/AudioContext.tsx @@ -32,7 +32,7 @@ export function AudioProvider({ children }: { children: ReactNode }) { // Create listener if not already present let listener = camera.children.find( - (child) => child instanceof AudioListener + (child) => child instanceof AudioListener, ) as AudioListener | undefined; if (!listener) { diff --git a/src/components/AudioEmitter.tsx b/src/components/AudioEmitter.tsx index 7cd32486..ab8de400 100644 --- a/src/components/AudioEmitter.tsx +++ b/src/components/AudioEmitter.tsx @@ -13,7 +13,7 @@ const audioBufferCache = new Map(); function getCachedAudioBuffer( audioUrl: string, audioLoader: any, - onLoad: (buffer: AudioBuffer) => void + onLoad: (buffer: AudioBuffer) => void, ) { if (audioBufferCache.has(audioUrl)) { onLoad(audioBufferCache.get(audioUrl)!); @@ -27,7 +27,7 @@ function getCachedAudioBuffer( undefined, (err: any) => { console.error("AudioEmitter: Audio load error", audioUrl, err); - } + }, ); } } @@ -41,16 +41,16 @@ export const AudioEmitter = memo(function AudioEmitter({ const fileName = getProperty(object, "fileName")?.value ?? ""; const volume = parseFloat(getProperty(object, "volume")?.value ?? "1"); const minDistance = parseFloat( - getProperty(object, "minDistance")?.value ?? "1" + getProperty(object, "minDistance")?.value ?? "1", ); const maxDistance = parseFloat( - getProperty(object, "maxDistance")?.value ?? "1" + getProperty(object, "maxDistance")?.value ?? "1", ); const minLoopGap = parseFloat( - getProperty(object, "minLoopGap")?.value ?? "0" + getProperty(object, "minLoopGap")?.value ?? "0", ); const maxLoopGap = parseFloat( - getProperty(object, "maxLoopGap")?.value ?? "0" + getProperty(object, "maxLoopGap")?.value ?? "0", ); const is3D = parseInt(getProperty(object, "is3D")?.value ?? "0"); diff --git a/src/components/CamerasProvider.tsx b/src/components/CamerasProvider.tsx index 49de1708..0f07b78a 100644 --- a/src/components/CamerasProvider.tsx +++ b/src/components/CamerasProvider.tsx @@ -71,7 +71,7 @@ export function CamerasProvider({ children }: { children: ReactNode }) { setCameraIndex(index); } }, - [cameraCount] + [cameraCount], ); useEffect(() => { @@ -83,7 +83,7 @@ export function CamerasProvider({ children }: { children: ReactNode }) { // Apply coordinate system correction for Torque3D to Three.js const correction = new Quaternion().setFromAxisAngle( new Vector3(0, 1, 0), - -Math.PI / 2 + -Math.PI / 2, ); camera.quaternion.copy(cameraInfo.rotation).multiply(correction); } @@ -97,7 +97,7 @@ export function CamerasProvider({ children }: { children: ReactNode }) { setCameraIndex: setCamera, cameraCount, }), - [registerCamera, unregisterCamera, nextCamera, setCamera, cameraCount] + [registerCamera, unregisterCamera, nextCamera, setCamera, cameraCount], ); return ( diff --git a/src/components/GenericShape.tsx b/src/components/GenericShape.tsx index bcb412e0..4dd3b07c 100644 --- a/src/components/GenericShape.tsx +++ b/src/components/GenericShape.tsx @@ -81,7 +81,7 @@ export const ShapeModel = memo(function ShapeModel() { const hullBoneIndices = useMemo(() => { const skeletonsFound = Object.values(nodes).filter( - (node: any) => node.skeleton + (node: any) => node.skeleton, ); if (skeletonsFound.length > 0) { @@ -97,12 +97,12 @@ export const ShapeModel = memo(function ShapeModel() { ([name, node]: [string, any]) => node.material && node.material.name !== "Unassigned" && - !node.name.match(/^Hulk/i) + !node.name.match(/^Hulk/i), ) .map(([name, node]: [string, any]) => { const geometry = filterGeometryByVertexGroups( node.geometry, - hullBoneIndices + hullBoneIndices, ); return { node, geometry }; }); diff --git a/src/components/InspectorControls.tsx b/src/components/InspectorControls.tsx index 5e5a15a2..234e976d 100644 --- a/src/components/InspectorControls.tsx +++ b/src/components/InspectorControls.tsx @@ -47,7 +47,7 @@ const groupedMissions = getMissionList().reduce( missionName: string; displayName: string; }> - >() + >(), ); groupedMissions.forEach((groupMissions, groupName) => { @@ -59,8 +59,8 @@ groupedMissions.forEach((groupMissions, groupName) => { (missionInfo) => (missionInfo.displayName || missionInfo.missionName).toLowerCase(), ], - ["asc"] - ) + ["asc"], + ), ); }); @@ -90,7 +90,7 @@ export function InspectorControls({ groupName === "Official" ? 0 : groupName == null ? 2 : 1, ([groupName]) => (groupName ? groupName.toLowerCase() : ""), ], - ["asc", "asc"] + ["asc", "asc"], ); return groups; }, []); @@ -125,7 +125,7 @@ export function InspectorControls({ ))} - ) + ), )}
diff --git a/src/components/InteriorInstance.tsx b/src/components/InteriorInstance.tsx index 325c832d..e69fbdd8 100644 --- a/src/components/InteriorInstance.tsx +++ b/src/components/InteriorInstance.tsx @@ -65,7 +65,7 @@ export const InteriorModel = memo( {Object.entries(nodes) .filter( ([name, node]: [string, any]) => - !node.material || !node.material.name.match(/\.\d+$/) + !node.material || !node.material.name.match(/\.\d+$/), ) .map(([name, node]: [string, any]) => ( @@ -73,7 +73,7 @@ export const InteriorModel = memo( {debugMode ? {interiorFile} : null} ); - } + }, ); function InteriorPlaceholder({ color }: { color: string }) { diff --git a/src/components/Item.tsx b/src/components/Item.tsx index 94e0d057..612224f4 100644 --- a/src/components/Item.tsx +++ b/src/components/Item.tsx @@ -50,7 +50,7 @@ function getDataBlockShape(dataBlock: string) { _caseInsensitiveLookup = Object.fromEntries( Object.entries(dataBlockToShapeName).map(([key, value]) => { return [key.toLowerCase(), value]; - }) + }), ); } return _caseInsensitiveLookup[dataBlock.toLowerCase()]; diff --git a/src/components/ObserverControls.tsx b/src/components/ObserverControls.tsx index 2b9a045b..73aba793 100644 --- a/src/components/ObserverControls.tsx +++ b/src/components/ObserverControls.tsx @@ -98,7 +98,7 @@ function CameraMovement() { // updates while mouse wheels have fewer updates but large deltas. Math.max( MIN_SPEED_ADJUSTMENT, - Math.min(MAX_SPEED_ADJUSTMENT, Math.abs(e.deltaY * 0.01)) + Math.min(MAX_SPEED_ADJUSTMENT, Math.abs(e.deltaY * 0.01)), ) * direction; setSpeedMultiplier((prev) => { diff --git a/src/components/SettingsProvider.tsx b/src/components/SettingsProvider.tsx index c2ad4e6b..6cea6e60 100644 --- a/src/components/SettingsProvider.tsx +++ b/src/components/SettingsProvider.tsx @@ -48,17 +48,17 @@ export function SettingsProvider({ children }: { children: ReactNode }) { audioEnabled, setAudioEnabled, }), - [fogEnabled, speedMultiplier, fov, audioEnabled] + [fogEnabled, speedMultiplier, fov, audioEnabled], ); const debugContext = useMemo( () => ({ debugMode, setDebugMode }), - [debugMode, setDebugMode] + [debugMode, setDebugMode], ); const controlsContext = useMemo( () => ({ speedMultiplier, setSpeedMultiplier }), - [speedMultiplier, setSpeedMultiplier] + [speedMultiplier, setSpeedMultiplier], ); // Read persisted settings from localStoarge. diff --git a/src/components/Sky.tsx b/src/components/Sky.tsx index e474e4fa..a3175e19 100644 --- a/src/components/Sky.tsx +++ b/src/components/Sky.tsx @@ -49,7 +49,7 @@ export function SkyBox({ FALLBACK_URL, FALLBACK_URL, ], - [detailMapList] + [detailMapList], ); const skyBox = useCubeTexture(skyBoxFiles, { path: "" }); diff --git a/src/components/StaticShape.tsx b/src/components/StaticShape.tsx index efcd6425..6d932e37 100644 --- a/src/components/StaticShape.tsx +++ b/src/components/StaticShape.tsx @@ -38,7 +38,7 @@ function getDataBlockShape(dataBlock: string) { _caseInsensitiveLookup = Object.fromEntries( Object.entries(dataBlockToShapeName).map(([key, value]) => { return [key.toLowerCase(), value]; - }) + }), ); } return _caseInsensitiveLookup[dataBlock.toLowerCase()]; diff --git a/src/components/TerrainBlock.tsx b/src/components/TerrainBlock.tsx index 6005cc4e..f3481cba 100644 --- a/src/components/TerrainBlock.tsx +++ b/src/components/TerrainBlock.tsx @@ -58,12 +58,12 @@ function BlendedTerrainTextures({ textureNames.map((name) => terrainTextureToUrl(name)), (textures) => { textures.forEach((tex) => setupColor(tex)); - } + }, ); const alphaTextures = useMemo( () => alphaMaps.map((data) => setupMask(data)), - [alphaMaps] + [alphaMaps], ); const tiling = useMemo( @@ -75,7 +75,7 @@ function BlendedTerrainTextures({ 4: 32, 5: 32, }), - [] + [], ); const onBeforeCompile = useCallback( @@ -89,7 +89,7 @@ function BlendedTerrainTextures({ debugMode, }); }, - [baseTextures, alphaTextures, visibilityMask, tiling, debugMode] + [baseTextures, alphaTextures, visibilityMask, tiling, debugMode], ); return ( @@ -125,7 +125,7 @@ function TerrainMaterial({ 256, 256, RedFormat, - FloatType + FloatType, ); displacementMap.colorSpace = NoColorSpace; displacementMap.generateMipmaps = false; @@ -168,7 +168,7 @@ function TerrainMaterial({ terrainSize, terrainSize, RedFormat, - UnsignedByteType + UnsignedByteType, ); visibilityMask.colorSpace = NoColorSpace; visibilityMask.wrapS = visibilityMask.wrapT = ClampToEdgeWrapping; @@ -211,7 +211,7 @@ export const TerrainBlock = memo(function TerrainBlock({ const squareSize = useMemo(() => { const squareSizeString: string | undefined = getProperty( object, - "squareSize" + "squareSize", )?.value; return squareSizeString ? parseInt(squareSizeString, 10) @@ -221,7 +221,7 @@ export const TerrainBlock = memo(function TerrainBlock({ const emptySquares: number[] = useMemo(() => { const emptySquaresString: string | undefined = getProperty( object, - "emptySquares" + "emptySquares", )?.value; return emptySquaresString diff --git a/src/components/Turret.tsx b/src/components/Turret.tsx index 43df5f74..92dcce9e 100644 --- a/src/components/Turret.tsx +++ b/src/components/Turret.tsx @@ -28,7 +28,7 @@ function getDataBlockShape(dataBlock: string) { _caseInsensitiveLookup = Object.fromEntries( Object.entries(dataBlockToShapeName).map(([key, value]) => { return [key.toLowerCase(), value]; - }) + }), ); } return _caseInsensitiveLookup[dataBlock.toLowerCase()]; @@ -54,7 +54,7 @@ export function Turret({ object }: { object: ConsoleObject }) { } if (!barrelShapeName) { console.error( - ` missing shape for initialBarrel dataBlock: ${initialBarrel}` + ` missing shape for initialBarrel dataBlock: ${initialBarrel}`, ); } diff --git a/src/components/useDistanceFromCamera.ts b/src/components/useDistanceFromCamera.ts index 5936a7c7..676b4ceb 100644 --- a/src/components/useDistanceFromCamera.ts +++ b/src/components/useDistanceFromCamera.ts @@ -4,7 +4,7 @@ import { Object3D } from "three"; import { useWorldPosition } from "./useWorldPosition"; export function useDistanceFromCamera( - ref: RefObject + ref: RefObject, ): RefObject { const { camera } = useThree(); const distanceRef = useRef(null); diff --git a/src/components/useWorldPosition.ts b/src/components/useWorldPosition.ts index 2a34582a..2af9bec7 100644 --- a/src/components/useWorldPosition.ts +++ b/src/components/useWorldPosition.ts @@ -3,7 +3,7 @@ import { useRef, RefObject } from "react"; import { Object3D, Vector3 } from "three"; export function useWorldPosition( - ref: RefObject + ref: RefObject, ): RefObject { const worldPositionRef = useRef(null); diff --git a/src/manifest.ts b/src/manifest.ts index eb27f5fe..4d49478e 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -41,7 +41,7 @@ export function getActualResourcePathUncached(resourcePath: string) { // First, try exact case-insensitive match const foundLowerCase = resourcePaths.find( - (s) => s.toLowerCase() === lowerCased + (s) => s.toLowerCase() === lowerCased, ); if (foundLowerCase) { return foundLowerCase; @@ -55,7 +55,7 @@ export function getActualResourcePathUncached(resourcePath: string) { if (pathWithoutNumber !== resourcePath) { // If we stripped a number, try to find the version without it const foundWithoutNumber = resourcePaths.find( - (s) => s.toLowerCase() === lowerCasedWithoutNumber + (s) => s.toLowerCase() === lowerCasedWithoutNumber, ); if (foundWithoutNumber) { return foundWithoutNumber; @@ -69,9 +69,9 @@ export function getActualResourcePathUncached(resourcePath: string) { s .replace( /^(textures\/)((lush|desert|badlands|lava|ice|jaggedclaw|terrainTiles)\/)/, - "$1" + "$1", ) - .toLowerCase() === lowerCased + .toLowerCase() === lowerCased, ); if (foundNested) { return foundNested; diff --git a/src/meshUtils.ts b/src/meshUtils.ts index ea4109ab..6ef51c87 100644 --- a/src/meshUtils.ts +++ b/src/meshUtils.ts @@ -23,7 +23,7 @@ export function getHullBoneIndices(skeleton: any): Set { */ export function filterGeometryByVertexGroups( geometry: any, - hullBoneIndices: Set + hullBoneIndices: Set, ): any { // If no hull bones or no skinning data, return original geometry if (hullBoneIndices.size === 0 || !geometry.attributes.skinIndex) { diff --git a/src/mission.ts b/src/mission.ts index 1c87e01f..2ab6da5f 100644 --- a/src/mission.ts +++ b/src/mission.ts @@ -55,7 +55,7 @@ function parseInstance(instance) { default: throw new Error( - `Unhandled value type: ${def.target.name} = ${def.value.type}` + `Unhandled value type: ${def.target.name} = ${def.value.type}`, ); } }), @@ -70,7 +70,7 @@ export function parseMissionScript(script) { // - Remove code-like parts of the script so it's easier to parse. script = script.replace( /(\/\/--- OBJECT WRITE END ---\s+)(?:.|[\r\n])*$/, - "$1" + "$1", ); let objectWriteBegin = /(\/\/--- OBJECT WRITE BEGIN ---\s+)/.exec(script); @@ -173,7 +173,7 @@ export function parseMissionScript(script) { globals: mission.sections .filter((section) => !section.name) .flatMap((section) => - section.definitions.filter((def) => def.type === "definition") + section.definitions.filter((def) => def.type === "definition"), ), }; } @@ -202,7 +202,7 @@ export function getTerrainBlock(mission: Mission): ConsoleObject { export function getTerrainFile(mission: Mission) { const terrainBlock = getTerrainBlock(mission); return terrainBlock.properties.find( - (prop) => prop.target.name === "terrainFile" + (prop) => prop.target.name === "terrainFile", ).value; } diff --git a/src/shaderMaterials.ts b/src/shaderMaterials.ts index 552caf93..a3460603 100644 --- a/src/shaderMaterials.ts +++ b/src/shaderMaterials.ts @@ -19,7 +19,7 @@ const alphaAsRoughnessShaderModifier = (shader: any) => { #ifdef USE_MAP roughnessFactor = texture2D(map, vMapUv).a * 1; #endif - ` + `, ); }; diff --git a/src/textureUtils.ts b/src/textureUtils.ts index 8e1bc50c..ae553941 100644 --- a/src/textureUtils.ts +++ b/src/textureUtils.ts @@ -30,7 +30,7 @@ export function setupMask(data) { 256, 256, RedFormat, // 1 channel - UnsignedByteType // 8-bit + UnsignedByteType, // 8-bit ); // Masks should stay linear @@ -132,7 +132,7 @@ float getWireframe(vec2 uv, float gridSize, float lineWidth) { if (visibility < 0.5) { discard; } - ` + `, ); } @@ -206,6 +206,6 @@ float getWireframe(vec2 uv, float gridSize, float lineWidth) { } else { diffuseColor.rgb = textureColor; } -` +`, ); } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..48ea18d5 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; +import { resolve } from "node:path"; + +export default defineConfig({ + test: { + include: ["**/*.spec.ts"], + }, + resolve: { + alias: { + "@": resolve(__dirname, "."), + }, + }, +});