diff --git a/.babelrc b/.babelrc index 3e7174a..89c75f5 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,13 @@ { "presets": [ - "@babel/preset-env", + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ], "@babel/preset-typescript" ] } \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..69a68a8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# EditorConfig helps maintain consistent coding styles +# https://editorconfig.org + +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# JavaScript, TypeScript +[*.{js,jsx,ts,tsx,mjs,cjs}] +indent_style = space +indent_size = 4 + +# JSON, YAML +[*.{json,yml,yaml}] +indent_style = space +indent_size = 2 + +# Markdown +[*.md] +trim_trailing_whitespace = false +max_line_length = 80 + +# Package files +[{package.json,*.lock}] +indent_style = space +indent_size = 2 + +# Makefiles +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cab3a51 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,21 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +build/ +coverage/ + +# Generated files +*.min.js +*.bundle.js +wasm/bclibc.js + +# WASM +*.wasm + +# Logs +*.log + +# OS +.DS_Store \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 9e3e6f9..a8b1f05 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -8,26 +8,40 @@ on: types: [created] jobs: - build: + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - node-version: 16 - - run: npm ci - - run: npm test + node-version: 20 + cache: "yarn" + - run: yarn install --frozen-lockfile + - run: yarn test publish-npm: - needs: build + needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + + - name: Init C++ source submodule + run: git submodule update --init lib/py-ballisticcalc + + - name: Setup Emscripten + uses: mymindstorm/setup-emsdk@v14 + + - name: Create emsdk stub for Makefile compatibility + run: mkdir -p lib/emsdk && echo "" > lib/emsdk/emsdk_env.sh + + - uses: actions/setup-node@v6 with: - node-version: 16 + node-version: 20 registry-url: https://registry.npmjs.org/ - - run: npm ci - - run: npm publish + cache: "yarn" + + - run: yarn install --frozen-lockfile + + - run: yarn publish env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c5d4e86..73d3427 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,17 +2,18 @@ name: Jest on: pull_request: - branches: - - '*' + branches: + - '*' workflow_dispatch: jobs: - build: + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - node-version: 16 - - run: npm ci - - run: npm test \ No newline at end of file + node-version: 20 + cache: 'yarn' + - run: yarn install --frozen-lockfile + - run: yarn test diff --git a/.gitignore b/.gitignore index 5cf02b5..6a467be 100644 --- a/.gitignore +++ b/.gitignore @@ -129,4 +129,7 @@ dist .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz -.pnp.* \ No newline at end of file +.pnp.* + +build/ +dist/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4a8fa08 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "emsdk"] + path = lib/emsdk + url = https://github.com/emscripten-core/emsdk.git +[submodule "lib/bclibc"] + path = lib/bclibc + url = https://github.com/ballistics-lab/bclibc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ce36c57 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +dist/ +build/ + +# Git submodules (add your submodule paths here) +**/submodules/ +vendor/ +third-party/ +# Add specific submodule paths if needed: +# path/to/your/submodule/ + +# Generated files +*.wasm +*.min.js +*.bundle.js + +# Coverage +coverage/ +.nyc_output/ + +# Logs +*.log + +# OS +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ + +# Emscripten generated +wasm/bclibc.js +wasm/*.wasm + +# Package manager +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Git +.git/ +.gitmodules + +# Submodules +bclibc/ +emsdk/ diff --git a/.prettierrc b/.prettierrc index ac25bba..63ddaeb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,37 @@ { "tabWidth": 4, "useTabs": false, - "jsxBracketSameLine": false + "semi": true, + "singleQuote": false, + "quoteProps": "as-needed", + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 100, + "endOfLine": "lf", + "overrides": [ + { + "files": "*.md", + "options": { + "printWidth": 80, + "proseWrap": "always" + } + }, + { + "files": "*.json", + "options": { + "tabWidth": 2 + } + }, + { + "files": [ + "*.yml", + "*.yaml" + ], + "options": { + "tabWidth": 2 + } + } + ], + "bracketSameLine": false } \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..5d6e50c --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/lib/**/", + "${workspaceFolder}/lib/emsdk/upstream/emscripten/cache/sysroot/include/**", + "${workspaceFolder}/lib/emsdk/upstream/emscripten/system/include/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index cefa436..e22a244 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,25 +1,35 @@ { - "cSpell.words": [ - "atmo" - ], "editor.tabSize": 4, "editor.insertSpaces": true, "editor.detectIndentation": false, "editor.formatOnSave": true, - "[javascript]": { - "editor.tabSize": 4, - "editor.insertSpaces": true + "jest.jestCommandLine": "yarn jest", + "jest.runMode": "watch", + "jest.nodeEnv": { + "NODE_OPTIONS": "--experimental-vm-modules --no-warnings" }, - "[typescript]": { - "editor.tabSize": 4, - "editor.insertSpaces": true + // TypeScript/JavaScript specific + "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.inlayHints.parameterNames.enabled": "all", + "js/ts.inlayHints.variableTypes.enabled": true, + "js/ts.inlayHints.propertyDeclarationTypes.enabled": true, + "js/ts.inlayHints.functionLikeReturnTypes.enabled": true, + "js/ts.preferences.includePackageJsonAutoImports": "auto", + "js/ts.suggest.autoImports": true, + "js/ts.updateImportsOnFileMove.enabled": "always", + "js/ts.preferences.importModuleSpecifier": "relative", + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "editor.suggest.showWords": true, + "editor.suggest.showKeywords": true, + "editor.quickSuggestions": { + "other": true, + "comments": false, + "strings": true }, - "[javascriptreact]": { - "editor.tabSize": 4, - "editor.insertSpaces": true - }, - "[typescriptreact]": { - "editor.tabSize": 4, - "editor.insertSpaces": true - } -} \ No newline at end of file + "editor.parameterHints.enabled": true, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "cmake.sourceDirectory": "${workspaceFolder}/lib/bclibc" +} diff --git a/FORMATTING.md b/FORMATTING.md new file mode 100644 index 0000000..5e54d48 --- /dev/null +++ b/FORMATTING.md @@ -0,0 +1,243 @@ +# Code Formatting Guide + +## Tools + +This project uses: + +- **Prettier** - Code formatting +- **ESLint** - Code linting +- **EditorConfig** - Editor consistency + +## Installation + +```bash +npm install -D prettier eslint eslint-config-prettier eslint-plugin-jest +``` + +Or with yarn: + +```bash +yarn add -D prettier eslint eslint-config-prettier eslint-plugin-jest +``` + +## Usage + +### Format all files + +```bash +npm run format +``` + +### Check formatting (CI) + +```bash +npm run format:check +``` + +### Lint code + +```bash +npm run lint +``` + +### Auto-fix linting issues + +```bash +npm run lint:fix +``` + +## VSCode Setup + +### Required Extensions + +1. **Prettier - Code formatter** (`esbenp.prettier-vscode`) +2. **ESLint** (`dbaeumer.vscode-eslint`) +3. **EditorConfig** (`editorconfig.editorconfig`) + +### Settings + +Your `.vscode/settings.json` is already configured: + +```json +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } +} +``` + +## Formatting Rules + +### JavaScript/TypeScript + +- **Indent**: 4 spaces +- **Quotes**: Double quotes +- **Semicolons**: Always +- **Line length**: 100 characters +- **Trailing commas**: ES5 (objects, arrays) + +### JSON/YAML + +- **Indent**: 2 spaces + +### Markdown + +- **Line length**: 80 characters +- **Prose wrap**: Always + +## Example + +**Before formatting:** + +```javascript +function test(a, b, c) { + const result = a + b + c; + return result; +} +``` + +**After formatting:** + +```javascript +function test(a, b, c) { + const result = a + b + c; + return result; +} +``` + +## Git Hooks (Optional) + +To format code automatically on commit, install Husky: + +```bash +npm install -D husky lint-staged +``` + +Add to `package.json`: + +```json +{ + "lint-staged": { + "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"], + "*.{json,css,md}": ["prettier --write"] + } +} +``` + +Setup hooks: + +```bash +npx husky init +echo "npx lint-staged" > .husky/pre-commit +``` + +## Ignoring Files + +### Prettier + +Edit `.prettierignore`: + +``` +node_modules/ +dist/ +*.wasm +``` + +### ESLint + +Edit `.eslintignore`: + +``` +node_modules/ +dist/ +wasm/bclibc.js +``` + +## CI/CD Integration + +Add to your CI pipeline: + +```yaml +# .github/workflows/ci.yml +- name: Check formatting + run: npm run format:check + +- name: Lint + run: npm run lint +``` + +## Manual Commands + +### Format specific file + +```bash +npx prettier --write path/to/file.js +``` + +### Check specific file + +```bash +npx prettier --check path/to/file.js +``` + +### Lint specific file + +```bash +npx eslint path/to/file.js +``` + +## Troubleshooting + +### Prettier not working in VSCode + +1. Check extension is installed: `Prettier - Code formatter` +2. Check settings: `"editor.defaultFormatter": "esbenp.prettier-vscode"` +3. Reload VSCode: `Ctrl+Shift+P` → "Reload Window" + +### Conflicts between ESLint and Prettier + +Already configured with `eslint-config-prettier` which disables conflicting +ESLint rules. + +### Format on save not working + +Check these settings: + +```json +{ + "editor.formatOnSave": true, + "prettier.requireConfig": true +} +``` + +Make sure `.prettierrc` exists in project root. + +## Team Workflow + +1. **Before commit**: Run `npm run format && npm run lint:fix` +2. **Code review**: Check formatting with `npm run format:check` +3. **CI**: Automatically checks formatting and linting + +## Customization + +To change formatting rules, edit `.prettierrc`: + +```json +{ + "tabWidth": 4, + "printWidth": 100, + "singleQuote": false +} +``` + +To change linting rules, edit `.eslintrc.json`: + +```json +{ + "rules": { + "no-console": "warn" + } +} +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dd8f14e --- /dev/null +++ b/Makefile @@ -0,0 +1,171 @@ +# Makefile +.PHONY: build build-wasm build-ts build-copy-html clean \ + clone-submodules install-emsdk setup-emsdk update-emsdk clean-emsdk \ + check-emsdk show-sources help + +# Source files +BCLIBC_SRC = lib/bclibc/src +BCLIBC_INCLUDE = lib/bclibc/include +WASM_OUT_DIR = ./build +DIST_DIR = ./dist + +# Find all C++ source files +CPP_SOURCES = $(wildcard $(BCLIBC_SRC)/*.cpp) + +# Emscripten paths +EMSDK_DIR = ./lib/emsdk +EMSDK_ENV = $(EMSDK_DIR)/emsdk_env.sh + +build: build-wasm build-ts build-copy-html + + +build-wasm: clone-submodules + @echo "🔨 Building WASM (ESM)..." + @mkdir -p $(WASM_OUT_DIR) + @bash -c "source $(EMSDK_ENV) && \ + (command -v emcc >/dev/null 2>&1 && echo '✅ Emscripten is available' || (echo '❌ Emscripten not found!' && exit 1)) && \ + emcc wasm/bindings.cpp $(CPP_SOURCES) -o $(WASM_OUT_DIR)/bclibc.js \ + --bind \ + --post-js wasm/post.js \ + -I$(BCLIBC_INCLUDE) \ + -s MODULARIZE=1 \ + -s EXPORT_ES6=1 \ + -s SINGLE_FILE=1 \ + -s ENVIRONMENT='web,node,worker' \ + --emit-tsd bclibc.d.ts \ + -O3 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -fexceptions \ + -s DISABLE_EXCEPTION_CATCHING=0" + @echo "✅ ESM WASM built in $(WASM_OUT_DIR)" + +build-ts: + @echo "🔨 Building TypeScript..." + # yarn tsc + yarn tsup ./src/index.ts --format esm,cjs --shims --no-splitting --dts + @echo "✅ TypeScript built!" + + +build-copy-html: + @echo "🔨 Copying Example..." + cp src/index.html dist/ + + +clean: + rm -rf dist lib + @echo "🧹 Cleaned!" + + +# ============================================================================ +# Submodules Setup +# ============================================================================ + +clone-submodules: + @echo "📂 Checking submodules..." + @if [ ! -f "$(BCLIBC_SRC)/main.cpp" ]; then \ + echo "📥 Cloning/Updating submodules (bclibc)..."; \ + git submodule update --init --recursive; \ + echo "✅ Submodules ready!"; \ + else \ + echo "✅ Submodules already present."; \ + fi + +# ============================================================================ +# Emscripten Setup +# ============================================================================ + +install-emsdk: + @echo "📦 Installing Emscripten SDK..." + @if [ -d "$(EMSDK_DIR)" ]; then \ + echo "⚠️ Emscripten SDK already exists at $(EMSDK_DIR)"; \ + echo " Run 'make clean-emsdk' first if you want to reinstall"; \ + else \ + git clone https://github.com/emscripten-core/emsdk.git $(EMSDK_DIR); \ + cd $(EMSDK_DIR) && ./emsdk install latest; \ + cd $(EMSDK_DIR) && ./emsdk activate latest; \ + echo ""; \ + echo "✅ Emscripten SDK installed!"; \ + echo ""; \ + echo "To activate Emscripten, run:"; \ + echo " source $(EMSDK_DIR)/emsdk_env.sh"; \ + echo ""; \ + echo "Or add this to your ~/.bashrc or ~/.zshrc:"; \ + echo " source $(PWD)/$(EMSDK_DIR)/emsdk_env.sh"; \ + fi + +setup-emsdk: install-emsdk + @echo "" + @echo "🔧 Emscripten Setup Instructions:" + @echo "" + @echo "1. Activate Emscripten in your current shell:" + @echo " source $(EMSDK_DIR)/emsdk_env.sh" + @echo "" + @echo "2. Verify installation:" + @echo " emcc --version" + @echo "" + @echo "3. (Optional) Add to your shell config for permanent activation:" + @echo " echo 'source $(PWD)/$(EMSDK_DIR)/emsdk_env.sh' >> ~/.bashrc" + @echo " # or for zsh:" + @echo " echo 'source $(PWD)/$(EMSDK_DIR)/emsdk_env.sh' >> ~/.zshrc" + @echo "" + @echo "4. Build the project:" + @echo " make build" + @echo "" + +update-emsdk: + @echo "🔄 Оновлення Emscripten SDK..." + @if [ -d "$(EMSDK_DIR)" ]; then \ + cd $(EMSDK_DIR) && \ + git fetch origin && \ + git checkout main || git checkout master && \ + git pull origin main || git pull origin master && \ + ./emsdk install latest && \ + ./emsdk activate latest && \ + echo "✅ Emscripten оновлено до останньої версії!"; \ + else \ + echo "❌ EMSDK не знайдено в $(EMSDK_DIR). Спробуйте 'make install-emsdk'"; \ + fi + +clean-emsdk: + @echo "🗑️ Removing Emscripten SDK..." + rm -rf $(EMSDK_DIR) + @echo "✅ Emscripten SDK removed!" + +# Check if emcc is available +check-emsdk: + @command -v emcc >/dev/null 2>&1 || { \ + echo "❌ Emscripten not found!"; \ + echo ""; \ + echo "Run 'make install-emsdk' to install, then:"; \ + echo " source $(EMSDK_DIR)/emsdk_env.sh"; \ + echo ""; \ + exit 1; \ + } + @echo "✅ Emscripten is available: $$(emcc --version | head -1)" + +# Debug: показати які файли будуть скомпільовані +show-sources: + @echo "C++ source files:" + @echo $(CPP_SOURCES) + +# Show all available commands +help: + @echo "Available commands:" + @echo "" + @echo " make build - Build everything (WASM + TypeScript)" + @echo " make build-wasm - Build only WASM" + @echo " make build-ts - Build only TypeScript" + @echo " make test-wasm - Run all WASM tests" + @echo " make test-wasm-vector - Run vector tests only" + @echo " make test-wasm-interp - Run interpolation tests only" + @echo " make clean - Clean build artifacts" + @echo "" + @echo "Emscripten setup:" + @echo " make install-emsdk - Install Emscripten SDK" + @echo " make setup-emsdk - Show setup instructions" + @echo " make check-emsdk - Check if Emscripten is available" + @echo " make clean-emsdk - Remove Emscripten SDK" + @echo "" + @echo "Debug:" + @echo " make show-sources - Show C++ source files" + @echo " make help - Show this help message" diff --git a/README.md b/README.md index 6c6231e..1d0f724 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # BallisticCalculator + ## ISC library for small arms ballistic calculations (JavaScript ES6+) + ![NPM version](https://img.shields.io/npm/v/js-ballistics?style=flat-square&logo=npm) ![License](https://img.shields.io/npm/l/js-ballistics?style=flat-square) [![Jest](https://github.com/o-murphy/js-ballistics/actions/workflows/tests.yml/badge.svg)](https://github.com/o-murphy/js-ballistics/actions/workflows/tests.yml) @@ -7,78 +9,79 @@ [![SWUbanner]][SWUBadge] [SWUbanner]: -https://img.shields.io/badge/made_in-Ukraine-ffd700.svg?labelColor=0057b7&style=flat-square - -[SWUBadge]: -https://stand-with-ukraine.pp.ua + https://img.shields.io/badge/made_in-Ukraine-ffd700.svg?labelColor=0057b7&style=flat-square +[SWUBadge]: https://stand-with-ukraine.pp.ua ### Table of contents -* **[Installation](#installation)** - -* **[About project](#about-project)** +- **[Installation](#installation)** +- **[About project](#about-project)** ## Installation ### Via NPM + ```shell npm i js-ballistics@latest ``` +### Via YARn + +```shell +yarn add js-ballistics@latest +``` + ### Load dynamically + ```html - - - - Your HTML Page - - - - - - + + + + Your HTML Page + + + + ``` ## About project -The library provides trajectory calculation for projectiles including for various -applications, including air rifles, bows, firearms, artillery and so on. +The library provides trajectory calculation for projectiles including for +various applications, including air rifles, bows, firearms, artillery and so on. -3DF model that is used in this calculator is rooted in old C sources of version 2 of the public version of JBM -calculator, ported to C#, optimized, fixed and extended with elements described in -Litz's "Applied Ballistics" book and from the friendly project of Alexandre Trofimov -and then ported to Go. +3DF model that is used in this calculator is rooted in old C sources of version +2 of the public version of JBM calculator, ported to C#, optimized, fixed and +extended with elements described in Litz's "Applied Ballistics" book and from +the friendly project of Alexandre Trofimov and then ported to Go. -Now it's also ported to python3 and expanded to support calculation trajectory by -multiple ballistics coefficients and using custom drag data (such as Doppler radar data ©Lapua, etc.) +Now it's also ported to python3 and expanded to support calculation trajectory +by multiple ballistics coefficients and using custom drag data (such as Doppler +radar data ©Lapua, etc.) Next stage is porting it to JavaScript @@ -96,6 +99,17 @@ The current status of the project is ALPHA version. #### RISK NOTICE -The library performs very limited simulation of a complex physical process and so it performs a lot of approximations. Therefore, the calculation results MUST NOT be considered as completely and reliably reflecting actual behavior or characteristics of projectiles. While these results may be used for educational purpose, they must NOT be considered as reliable for the areas where incorrect calculation may cause making a wrong decision, financial harm, or can put a human life at risk. - -THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +The library performs very limited simulation of a complex physical process and +so it performs a lot of approximations. Therefore, the calculation results MUST +NOT be considered as completely and reliably reflecting actual behavior or +characteristics of projectiles. While these results may be used for educational +purpose, they must NOT be considered as reliable for the areas where incorrect +calculation may cause making a wrong decision, financial harm, or can put a +human life at risk. + +THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. diff --git a/__tests__/atmosphere.test.ts b/__tests__/atmosphere.test.ts index fc44939..2a2bdec 100644 --- a/__tests__/atmosphere.test.ts +++ b/__tests__/atmosphere.test.ts @@ -1,5 +1,4 @@ import { - Calculator, Atmo, Temperature, Pressure, @@ -7,12 +6,17 @@ import { UNew, Ammo, DragModel, - Table, + DragTables, Weapon, - Shot, - EulerIntegrationEngine, - RK4IntegrationEngine, + IntegrationMethod, } from "../src"; +import { Calculator } from "../src/interface"; +import { Shot } from "../src/shot"; + +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, +]; describe("Atmo Class Tests", () => { let standard: Atmo; @@ -32,20 +36,14 @@ describe("Atmo Class Tests", () => { }); test("Standard atmosphere properties", () => { - expect(standard.temperature.In(Temperature.Fahrenheit)).toBeCloseTo( - 59.0, - 1, - ); + expect(standard.temperature.In(Temperature.Fahrenheit)).toBeCloseTo(59.0, 1); expect(standard.pressure.In(Pressure.hPa)).toBeCloseTo(1013.25, 1); expect(standard.densityImperial).toBeCloseTo(0.076474, 4); }); test("High altitude properties (ICAO and ISA)", () => { // Ref https://www.engineeringtoolbox.com/standard-atmosphere-d_604.html - expect(highICAO.temperature.In(Temperature.Fahrenheit)).toBeCloseTo( - 23.36, - 1, - ); + expect(highICAO.temperature.In(Temperature.Fahrenheit)).toBeCloseTo(23.36, 1); expect(highICAO.densityRatio).toBeCloseTo(0.7387, 3); // Ref https://www.engineeringtoolbox.com/international-standard-atmosphere-d_985.html expect(highISA.pressure.In(Pressure.hPa)).toBeCloseTo(899, 0); @@ -57,51 +55,27 @@ describe("Atmo Class Tests", () => { expect(Atmo.machF(59)).toBeCloseTo(1116.15, 0); expect(Atmo.machF(10)).toBeCloseTo(1062.11, 0); expect(Atmo.machF(99)).toBeCloseTo(1158.39, 0); - expect(Atmo.machC(-20)).toBeCloseTo(318.94, 1); expect(highISA.mach.In(Velocity.MPS)).toBeCloseTo(336.4, 1); }); - test("altitude", () => { - // TODO: should warn - const _atmo = new Atmo(); - _atmo.getDensityFactorAndMachForAltitude(100_000); - }); - test("density", () => { expect(Atmo.calculateAirDensity(20, 1013, 0)).toBeCloseTo(1.20383, 4); expect(Atmo.calculateAirDensity(20, 1013, 1)).toBeCloseTo(1.19332, 4); }); - test("changes", () => { - expect(standard.temperatureAtAltitude(5000)).toBeLessThan( - standard.temperature.In(Temperature.Celsius), - ); - expect(standard.pressureAtAltitude(5000)).toBeLessThan( - standard.pressure.In(Pressure.hPa), - ); - const [density_ratio, mach] = - standard.getDensityFactorAndMachForAltitude(5000); - expect(density_ratio).toBeLessThan(standard.densityRatio); - expect(mach).toBeLessThan(standard.mach.In(Velocity.FPS)); - }); - - const calculators = [ - { engine: EulerIntegrationEngine }, // Assuming your Calculator constructor can take an 'engine' option - { engine: RK4IntegrationEngine }, - ]; - - test.each(calculators)("trajectory effects %s", ({ engine }) => { + test.each(methods)("trajectory effects $name", async (obj) => { + const { method } = obj; const check_distance = UNew.Yard(1000); const ammo = new Ammo({ - dm: new DragModel({ bc: 0.22, dragTable: Table.G7 }), + dm: new DragModel({ bc: 0.22, dragTable: DragTables.G7 }), mv: UNew.FPS(3000), }); const weapon = new Weapon(); const atmo = new Atmo({ altitude: 0 }); // Start with standard sea-level atmosphere // Set baseline to zero at 1000 yards const zero = new Shot({ weapon, ammo, atmo }); - const calc = new Calculator({ engine }); - const baseline_trajectory = calc.fire({ + const calc = new Calculator({ method }); + const baseline_trajectory = await calc.fire({ shot: zero, trajectoryRange: check_distance, trajectoryStep: check_distance, @@ -110,38 +84,32 @@ describe("Atmo Class Tests", () => { // Increasing humidity reduces air density which decreases drag atmo.humidity = 1.0; - const tNumid = calc.fire({ + const tNumid = await calc.fire({ shot: new Shot({ weapon, ammo, atmo }), trajectoryRange: check_distance, trajectoryStep: check_distance, }); - expect(tNumid.getAtDistance(check_distance).time).toBeLessThan( - baseline.time, - ); + expect(tNumid.getAtDistance(check_distance).time).toBeLessThan(baseline.time); // Increasing temperature reduces air density which decreases drag const warm = new Atmo({ altitude: 0, temperature: UNew.Fahrenheit(120), }); - const tWarm = calc.fire({ + const tWarm = await calc.fire({ shot: new Shot({ weapon, ammo, atmo: warm }), trajectoryRange: check_distance, trajectoryStep: check_distance, }); - expect(tWarm.getAtDistance(check_distance).time).toBeLessThan( - baseline.time, - ); + expect(tWarm.getAtDistance(check_distance).time).toBeLessThan(baseline.time); // Increasing altitude reduces air density which decreases drag const high = new Atmo({ altitude: UNew.Foot(5000) }); // simulate increased altitude - const tHight = calc.fire({ + const tHight = await calc.fire({ shot: new Shot({ weapon, ammo, atmo: high }), trajectoryRange: check_distance, trajectoryStep: check_distance, }); - expect(tHight.getAtDistance(check_distance).time).toBeLessThan( - baseline.time, - ); + expect(tHight.getAtDistance(check_distance).time).toBeLessThan(baseline.time); }); }); diff --git a/__tests__/computer.test.ts b/__tests__/computer.test.ts index bdce3c6..bf364a9 100644 --- a/__tests__/computer.test.ts +++ b/__tests__/computer.test.ts @@ -1,33 +1,32 @@ import { expect, describe, test, beforeEach } from "@jest/globals"; import { - Calculator, Ammo, Wind, Atmo, DragModel, - Table, + DragTables, UNew, Weapon, - Shot, - HitResult, - EulerIntegrationEngine, - RK4IntegrationEngine, - TrajectoryRangeError, + RangeError, TrajectoryData, + IntegrationMethod, } from "../src"; +import { Calculator } from "../src/interface"; +import { Shot } from "../src/shot"; -const calculators = [ - { engine: EulerIntegrationEngine }, // Assuming these are string identifiers or actual classes - { engine: RK4IntegrationEngine }, +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, ]; -describe.each(calculators)("TestComputer %s", ({ engine }) => { - const calc: Calculator = new Calculator({ engine }); +describe.each(methods)("TestComputer $name", (obj) => { + const { method } = obj; + const calc: Calculator = new Calculator({ method }); const range: number = 1000; const step: number = 100; const dm: DragModel = new DragModel({ bc: 0.22, - dragTable: Table.G7, + dragTable: DragTables.G7, weight: 168, diameter: 0.308, length: 1.22, @@ -40,17 +39,23 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { ammo: ammo, atmo: atmo, }); - const baselineTrajectory: TrajectoryData[] = calc.fire({ - shot: baselineShot, - trajectoryRange: range, - trajectoryStep: step, - }).trajectory; + + let baselineTrajectory: TrajectoryData[]; + + beforeAll(async () => { + const hit = await calc.fire({ + shot: baselineShot, + trajectoryRange: range, + trajectoryStep: step, + }); + baselineTrajectory = hit.trajectory; + }); beforeEach(() => {}); // region Cant_angle - test("cant_zero_elevation", () => { + test("cant_zero_elevation", async () => { // Create a copy of the baseline shot and apply the cant_angle const cantedShot = new Shot({ ...baselineShot, @@ -58,28 +63,27 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { cantedShot.cantAngle = UNew.Degree(90); // Fire the canted shot - const cantedTrajectory = calc.fire({ + const hit = await calc.fire({ shot: cantedShot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const cantedTrajectory = hit.trajectory; // Perform the assertion comparing height and windage adjustments // console.log(cantedTrajectory[5].height.rawValue); // console.log(baselineShot.weapon.sightHeight.rawValue); // console.log(baselineTrajectory[5].height.rawValue); expect( - cantedTrajectory[5].height.rawValue - - baselineShot.weapon.sightHeight.rawValue, + cantedTrajectory[5].height.rawValue - baselineShot.weapon.sightHeight.rawValue ).toBeCloseTo(baselineTrajectory[5].height.rawValue, 1e-2); expect( - cantedTrajectory[5].windage.rawValue + - baselineShot.weapon.sightHeight.rawValue, + cantedTrajectory[5].windage.rawValue + baselineShot.weapon.sightHeight.rawValue ).toBeCloseTo(baselineTrajectory[5].windage.rawValue, 1e-2); }); - test("cant_positive_elevation", () => { + test("cant_positive_elevation", async () => { const canted = new Shot({ weapon: new Weapon({ sightHeight: weapon.sightHeight, @@ -91,16 +95,17 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { atmo: atmo, }); - const t = calc.fire({ + const hit = await calc.fire({ shot: canted, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const t = hit.trajectory; // Assert height difference with baseline expect(t[5].height.rawValue - weapon.sightHeight.rawValue).toBeCloseTo( baselineTrajectory[5].height.rawValue, - 2, + 2 ); // Assert windage at muzzle @@ -111,7 +116,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Cant_angle test with zero sight height and 90 degrees cant angle - test("cant_zero_sight_height", () => { + test("cant_zero_sight_height", async () => { // Create a new shot with the same parameters but a cant angle of 90 degrees const cantedShot = new Shot({ weapon: new Weapon({ @@ -123,21 +128,23 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { cantAngle: UNew.Degree(90), }); - const cantedTrajectory = calc.fire({ + const hit = await calc.fire({ shot: cantedShot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const cantedTrajectory = hit.trajectory; // Assert that height difference matches the baseline with adjusted sight height - expect( - cantedTrajectory[5].height.rawValue - weapon.sightHeight.rawValue, - ).toBeCloseTo(baselineTrajectory[5].height.rawValue, 2); + expect(cantedTrajectory[5].height.rawValue - weapon.sightHeight.rawValue).toBeCloseTo( + baselineTrajectory[5].height.rawValue, + 2 + ); // Assert windage has no significant change expect(cantedTrajectory[5].windage.rawValue).toBeCloseTo( baselineTrajectory[5].windage.rawValue, - 2, + 2 ); }); @@ -145,7 +152,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // region Wind // Wind from left should increase windage - test("wind_from_left", () => { + test("wind_from_left", async () => { // Create a shot with wind coming from the left const windFromLeft = new Wind({ velocity: UNew.MPH(5), @@ -159,20 +166,21 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { winds: [windFromLeft], }); - const trajectoryWithWind = calc.fire({ + const hit = await calc.fire({ shot: shotWithWind, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithWind = hit.trajectory; // Assert that the windage is greater due to wind from the left expect(trajectoryWithWind[5].windage.rawValue).toBeGreaterThan( - baselineTrajectory[5].windage.rawValue, + baselineTrajectory[5].windage.rawValue ); }); // Wind from right should decrease windage - test("wind_from_right", () => { + test("wind_from_right", async () => { // Create a shot with wind coming from the right const windFromRight = new Wind({ velocity: UNew.MPH(5), @@ -186,20 +194,21 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { winds: [windFromRight], }); - const trajectoryWithWind = calc.fire({ + const hit = await calc.fire({ shot: shotWithWind, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithWind = hit.trajectory; // Assert that the windage is less due to wind from the right expect(trajectoryWithWind[5].windage.rawValue).toBeLessThan( - baselineTrajectory[5].windage.rawValue, + baselineTrajectory[5].windage.rawValue ); }); // Wind from behind should decrease drop - test("wind_from_back", () => { + test("wind_from_back", async () => { // Create a shot with wind coming from behind const windFromBack = new Wind({ velocity: UNew.MPH(5), @@ -213,20 +222,21 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { winds: [windFromBack], }); - const trajectoryWithWind = calc.fire({ + const hit = await calc.fire({ shot: shotWithWind, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithWind = hit.trajectory; // Assert that the trajectory height is greater with wind from behind expect(trajectoryWithWind[5].height.rawValue).toBeGreaterThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); // Wind from in front should increase drop - test("wind_from_front", () => { + test("wind_from_front", async () => { // Create a shot with wind coming from the front const windFromFront = new Wind({ velocity: UNew.MPH(5), @@ -240,20 +250,21 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { winds: [windFromFront], }); - const trajectoryWithWind = calc.fire({ + const hit = await calc.fire({ shot: shotWithWind, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithWind = hit.trajectory; // Assert that the trajectory height is less with wind from the front expect(trajectoryWithWind[5].height.rawValue).toBeLessThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); // Wind from in front should increase drop - test("multi_winds", () => { + test("multi_winds", async () => { const shot = new Shot({ weapon, ammo, @@ -272,20 +283,18 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { ], }); - calc.fire({ shot, trajectoryRange: range, trajectoryStep: step }); - const t = calc.fire({ + const hit = await calc.fire({ shot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const t = hit.trajectory; - expect(t[5].windage.rawValue).toBeLessThan( - baselineTrajectory[5].windage.rawValue, - ); + expect(t[5].windage.rawValue).toBeLessThan(baselineTrajectory[5].windage.rawValue); }); // Wind from in front should increase drop - test("no_winds", () => { + test("no_winds", async () => { const shot1 = new Shot({ weapon, ammo, @@ -299,27 +308,19 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { winds: [], }); - calc.fire({ + const hit1 = await calc.fire({ shot: shot1, trajectoryRange: range, trajectoryStep: step, }); - calc.fire({ + const hit2 = await calc.fire({ shot: shot2, trajectoryRange: range, trajectoryStep: step, }); - const trajectory1 = calc.fire({ - shot: shot1, - trajectoryRange: range, - trajectoryStep: step, - }).trajectory; - const trajectory2 = calc.fire({ - shot: shot2, - trajectoryRange: range, - trajectoryStep: step, - }).trajectory; + const trajectory1 = hit1.trajectory; + const trajectory2 = hit2.trajectory; expect(trajectory1.length).toBeGreaterThan(0); expect(trajectory2.length).toBeGreaterThan(0); @@ -328,7 +329,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // end region Wind // region Twist - test("no_twist", () => { + test("no_twist", async () => { // Create a shot with no twist const shotWithNoTwist = new Shot({ weapon: new Weapon({ twist: 0 }), @@ -336,17 +337,18 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { atmo: atmo, }); - const trajectoryWithNoTwist = calc.fire({ + const hit = await calc.fire({ shot: shotWithNoTwist, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithNoTwist = hit.trajectory; // Assert that the windage is 0 with no twist expect(trajectoryWithNoTwist[5].windage.rawValue).toBe(0); }); - test("twist", () => { + test("twist", async () => { // Create a shot with right-hand twist const shotRightTwist = new Shot({ weapon: new Weapon({ twist: 12 }), // Positive twist rate @@ -355,11 +357,12 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate trajectory for right-hand twist - const trajectoryRightTwist = calc.fire({ + const hit1 = await calc.fire({ shot: shotRightTwist, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryRightTwist = hit1.trajectory; // Assert that windage is positive with right-hand twist expect(trajectoryRightTwist[5].windage.rawValue).toBeGreaterThan(0); @@ -372,18 +375,19 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate trajectory for left-hand twist - const trajectoryLeftTwist = calc.fire({ + const hit2 = await calc.fire({ shot: shotLeftTwist, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryLeftTwist = hit2.trajectory; // Assert that windage is negative with left-hand twist expect(trajectoryLeftTwist[5].windage.rawValue).toBeLessThan(0); // Assert that faster twist (right-hand twist) produces less drift compared to slower twist (left-hand twist) expect(-trajectoryLeftTwist[5].windage.rawValue).toBeGreaterThan( - trajectoryRightTwist[5].windage.rawValue, + trajectoryRightTwist[5].windage.rawValue ); }); @@ -391,7 +395,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // region Atmo - test("humidity", () => { + test("humidity", async () => { // Create an atmosphere with 90% humidity const humidAtmo = new Atmo({ humidity: 0.9 }); @@ -403,19 +407,20 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot with humidity - const trajectoryWithHumidity = calc.fire({ + const hit = await calc.fire({ shot: shotWithHumidity, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithHumidity = hit.trajectory; // Assert that height is greater with increased humidity expect(trajectoryWithHumidity[5].height.rawValue).toBeGreaterThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); - test("temperature_atmo", () => { + test("temperature_atmo", async () => { // Create an atmosphere with temperature at 0°C const coldAtmo = new Atmo({ temperature: UNew.Celsius(0) }); @@ -427,19 +432,20 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot in cold weather - const trajectoryInCold = calc.fire({ + const hit = await calc.fire({ shot: shotInCold, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryInCold = hit.trajectory; // Assert that the height is less in colder temperature, indicating increased drop expect(trajectoryInCold[5].height.rawValue).toBeLessThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); - test("altitude", () => { + test("altitude", async () => { // Create an atmosphere with altitude at 5000 feet const highAtmo = Atmo.icao({ altitude: UNew.Foot(5000) }); @@ -451,19 +457,20 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot at high altitude - const trajectoryAtHighAltitude = calc.fire({ + const hit = await calc.fire({ shot: shotAtHighAltitude, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryAtHighAltitude = hit.trajectory; // Assert that the height is greater at higher altitude, indicating decreased drop expect(trajectoryAtHighAltitude[5].height.rawValue).toBeGreaterThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); - test("pressure", () => { + test("pressure", async () => { // Create an atmosphere with pressure at 20.0 inHg const thinAtmo = new Atmo({ pressure: UNew.InHg(20.0) }); @@ -475,15 +482,16 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot in low pressure - const trajectoryInLowPressure = calc.fire({ + const hit = await calc.fire({ shot: shotInLowPressure, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryInLowPressure = hit.trajectory; // Assert that the height is greater in lower pressure, indicating decreased drop expect(trajectoryInLowPressure[5].height.rawValue).toBeGreaterThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); @@ -491,7 +499,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // region Ammo - test("ammo_drag", () => { + test("ammo_drag", async () => { // Create a new DragModel with increased ballistic coefficient (bc) const increasedDragModel = new DragModel({ bc: dm.bc + 0.5, @@ -515,19 +523,20 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot with slick ammo - const trajectoryWithSlickAmmo = calc.fire({ + const hit = await calc.fire({ shot: shotWithSlickAmmo, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithSlickAmmo = hit.trajectory; // Assert that the height is greater with the increased ballistic coefficient, indicating decreased drop expect(trajectoryWithSlickAmmo[5].height.rawValue).toBeGreaterThan( - baselineTrajectory[5].height.rawValue, + baselineTrajectory[5].height.rawValue ); }); - test("ammo_optional", () => { + test("ammo_optional", async () => { // Create a new DragModel with only the ballistic coefficient const reducedDragModel = new DragModel({ bc: dm.bc, @@ -548,58 +557,60 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { }); // Calculate the trajectory for the shot with the reduced ammo - const trajectoryWithReducedAmmo = calc.fire({ + const hit = await calc.fire({ shot: shotWithReducedAmmo, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const trajectoryWithReducedAmmo = hit.trajectory; // Assert that the height is the same as with the baseline, indicating no change in drop expect(trajectoryWithReducedAmmo[5].height.rawValue).toBeCloseTo( baselineTrajectory[5].height.rawValue, - 1e-2, + 1e-2 ); }); - test("powder_sensitivity", () => { + test("powder_sensitivity", async () => { ammo.calcPowderSens(UNew.FPS(2550), UNew.Celsius(0)); // Test case 1: Don't use powder sensitivity ammo.usePowderSensitivity = false; const coldNoSens = new Atmo({ temperature: UNew.Celsius(-5) }); const shotNoSens = new Shot({ weapon, ammo, atmo: coldNoSens }); - const tNoSens = calc.fire({ + const hit1 = await calc.fire({ shot: shotNoSens, trajectoryRange: range, trajectoryStep: step, - }).trajectory; - expect(tNoSens[0].velocity.rawValue).toBeCloseTo( - baselineTrajectory[0].velocity.rawValue, - ); + }); + const tNoSens = hit1.trajectory; + expect(tNoSens[0].velocity.rawValue).toBeCloseTo(baselineTrajectory[0].velocity.rawValue); // Test case 2: Powder temperature the same as atmosphere temperature ammo.usePowderSensitivity = true; const coldSameTemp = new Atmo({ temperature: UNew.Celsius(-5) }); const shotSameTemp = new Shot({ weapon, ammo, atmo: coldSameTemp }); - const tSameTemp = calc.fire({ + const hit2 = await calc.fire({ shot: shotSameTemp, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const tSameTemp = hit2.trajectory; expect(tSameTemp[0].velocity.rawValue).toBeLessThan( - baselineTrajectory[0].velocity.rawValue, + baselineTrajectory[0].velocity.rawValue ); // Test case 3: Different powder temperature - const coldDiffTemp = new Atmo({ powderT: UNew.Celsius(-5) }); + const coldDiffTemp = new Atmo({ powderTemperature: UNew.Celsius(-5) }); const shotDiffTemp = new Shot({ weapon, ammo, atmo: coldDiffTemp }); - const tDiffTemp = calc.fire({ + const hit3 = await calc.fire({ shot: shotDiffTemp, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const tDiffTemp = hit3.trajectory; expect(tDiffTemp[0].velocity.rawValue).toBeLessThan( - baselineTrajectory[0].velocity.rawValue, + baselineTrajectory[0].velocity.rawValue ); ammo.usePowderSensitivity = false; @@ -607,7 +618,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // end region Ammo - test("zero_velocity", () => { + test("zero_velocity", async () => { const tdm = new DragModel({ bc: dm.bc + 0.5, dragTable: dm.dragTable, @@ -624,7 +635,7 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { trajectoryStep: step, }); } catch (e: unknown) { - if (e instanceof TrajectoryRangeError) { + if (e instanceof RangeError) { console.log("Passing"); } else { throw e; @@ -632,14 +643,14 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { } }); - test("very_short_shot", () => { + test("very_short_shot", async () => { const tShot = new Shot({ weapon, ammo, atmo }); - const hitResult = calc.fire({ shot: tShot, trajectoryRange: range }); + const hitResult = await calc.fire({ shot: tShot, trajectoryRange: range }); expect(hitResult.length).toBeGreaterThan(1); }); // region Shot - test("winds_sort", () => { + test("winds_sort", async () => { // Create an array of Wind instances with varying distances const winds = [ new Wind({ @@ -666,8 +677,8 @@ describe.each(calculators)("TestComputer %s", ({ engine }) => { // Create a Shot instance with the winds array const shot = new Shot({ - weapon: undefined, - ammo: undefined, + weapon: undefined as unknown as Weapon, + ammo: undefined as unknown as Ammo, lookAngle: 0, relativeAngle: 0, cantAngle: 0, diff --git a/__tests__/danger-space.test.ts b/__tests__/danger-space.test.ts deleted file mode 100644 index a634991..0000000 --- a/__tests__/danger-space.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - Calculator, - HitResult, - Ammo, - UNew, - DragModel, - Shot, - Table, - Distance, - Weapon, - Wind, - EulerIntegrationEngine, - RK4IntegrationEngine, - Angular, // Ensure Angular is imported for UNew.Degree - TrajFlag, // Import TrajFlag for the extra data test -} from "../src"; -import { expect, describe, test, beforeEach } from "@jest/globals"; // Import beforeEach - -// Define the engine types to test with, similar to pytest's parameterized fixtures -const calculatorsToTest = [ - // Provide a name for better test output - { engine: EulerIntegrationEngine }, - { engine: RK4IntegrationEngine }, -]; - -// Use describe.each to iterate over different engine implementations -describe.each(calculatorsToTest)("TestDangerSpace with %s", ({ engine }) => { - // Declare variables that will hold the setup state, - // making them accessible across test cases within this describe block. - let lookAngle: Angular; - let shotResult: HitResult; - - // The beforeEach hook runs before each 'test' function in this describe block. - // This effectively mimics pytest's `setup_method` fixture with `autouse=True` in Python. - beforeEach(() => { - // Initialize the look angle - lookAngle = UNew.Degree(0); - - // Create the DragModel - const dm = new DragModel({ - bc: 0.223, - dragTable: Table.G7, - weight: 168, - diameter: 0.308, - length: UNew.Inch(1.282), - }); - - // Create Ammo and calculate powder sensitivity - const ammo = new Ammo({ - dm: dm, - mv: UNew.FPS(2750), - powderTemp: UNew.Celsius(15), - }); - ammo.calcPowderSens(2723, 0); - - // Create current winds - const currentWinds = [new Wind({ velocity: 2, directionFrom: 90 })]; - - // Create Shot and Calculator - const shot = new Shot({ - // ВИПРАВЛЕНО: Додано sightHeight: UNew.Inch(1) для узгодження з Python-тестом - weapon: new Weapon({ sightHeight: UNew.Inch(1) }), - ammo: ammo, - winds: currentWinds, - }); - - // Instantiate the Calculator with the current engine class - const calc = new Calculator({ engine }); - calc.setWeaponZero(shot, UNew.Foot(300)); - - // Fire the shot and store the result in the shared 'shotResult' variable - shotResult = calc.fire({ - shot: shot, - trajectoryRange: UNew.Yard(1000), - trajectoryStep: UNew.Yard(1), - extraData: true, - }); - }); - - // Define the test case for danger space calculation. - test("should calculate danger space correctly", () => { - // First test case for danger space calculation (Target height: 1.5 meters) - let dangerSpace = shotResult.dangerSpace( - UNew.Yard(500), - UNew.Meter(1.5), - lookAngle, - ); - - // Assertions using Jest's toBeCloseTo, mirroring pytest.approx's behavior. - // Precision `0` means checking to the nearest whole number (equivalent to abs=0.5). - // Expected values are now reverted to the original Python test values. - expect(dangerSpace.begin.distance.In(Distance.Yard)).toBeCloseTo( - 388.0, // Reverted to original Python test value - 0, - ); - expect(dangerSpace.end.distance.In(Distance.Yard)).toBeCloseTo( - 581.0, // Reverted to original Python test value - 0, - ); - - // Second test case for danger space calculation with different target height (10 inches) - dangerSpace = shotResult.dangerSpace( - UNew.Yard(500), - UNew.Inch(10), - lookAngle, - ); - - // Expected values are now reverted to the original Python test values. - expect(dangerSpace.begin.distance.In(Distance.Yard)).toBeCloseTo( - 483.0, // Reverted to original Python test value - 0, - ); - expect(dangerSpace.end.distance.In(Distance.Yard)).toBeCloseTo( - 516.0, // Reverted to original Python test value - 0, - ); - }); - - // Additional test case to ensure extra data flags are present, mirroring Python's test_extra_data. - test("should include extra data flags for zero and mach crossings", () => { - let seenZeroUp = false; - let seenZeroDown = false; - let seenMach = false; - - for (const p of shotResult.trajectory) { - // Use bitwise AND to check if the flag is set - if ((p.flag & TrajFlag.ZERO_UP) === TrajFlag.ZERO_UP) { - seenZeroUp = true; - } - if ((p.flag & TrajFlag.ZERO_DOWN) === TrajFlag.ZERO_DOWN) { - seenZeroDown = true; - } - if ((p.flag & TrajFlag.MACH) === TrajFlag.MACH) { - seenMach = true; - } - } - // These expectations remain true as the previous fix for Weapon sightHeight - // should have enabled these flags to be set. - expect(seenZeroUp).toBe(true); - expect(seenZeroDown).toBe(true); - expect(seenMach).toBe(true); - }); -}); diff --git a/__tests__/exception-basic.test.ts b/__tests__/exception-basic.test.ts new file mode 100644 index 0000000..385eeef --- /dev/null +++ b/__tests__/exception-basic.test.ts @@ -0,0 +1,96 @@ +/** + * Basic Exception Handling Test + * + * Tests C++ exception to JavaScript exception conversion + */ + +import { WasmManager } from "../src/_wasm"; + +describe("Basic Exception Handling", () => { + test("C++ runtime_error should be caught as JavaScript Error", async () => { + const bclibc = await WasmManager.init(); + + const testMessage = "Test exception from C++"; + + expect(() => { + bclibc.testThrowRuntimeError(testMessage); + }).toThrow(); + }); + + test("C++ exception should contain the original message", async () => { + const bclibc = await WasmManager.init(); + + const testMessage = "Custom error message"; + + try { + bclibc.testThrowRuntimeError(testMessage); + fail("Should have thrown an error"); + } catch (error) { + // Debug: see what we actually get + console.log("Caught error:", error); + console.log("Error type:", typeof error); + console.log("Error constructor:", error?.constructor?.name); + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain(testMessage); + } + }); + + test("C++ exception should have Error type", async () => { + const bclibc = await WasmManager.init(); + + try { + bclibc.testThrowRuntimeError("test"); + fail("Should have thrown an error"); + } catch (error) { + // In JavaScript, all C++ exceptions come through as Error objects + expect(error).toBeInstanceOf(Error); + expect(typeof error).toBe("object"); + } + }); +}); + +describe("Custom C++ Exception Handling", () => { + test("Custom C++ exception should be caught as JavaScript Error", async () => { + const bclibc = await WasmManager.init(); + + expect(() => { + bclibc.testThrowCustomException("Custom exception", 42.5, 100); + }).toThrow(); + }); + + test("Custom C++ exception should preserve custom fields", async () => { + const bclibc = await WasmManager.init(); + + const testMessage = "Exception with custom fields"; + const testValue = 123.456; + const testCount = 999; + + try { + bclibc.testThrowCustomException(testMessage, testValue, testCount); + fail("Should have thrown an error"); + } catch (error: any) { + // Check it's an Error + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain(testMessage); + + // Check custom fields are preserved + expect(error.customValue).toBe(testValue); + expect(error.customCount).toBe(testCount); + } + }); + + test("Custom exception fields should have correct types", async () => { + const bclibc = await WasmManager.init(); + + try { + bclibc.testThrowCustomException("test", 3.14, 42); + fail("Should have thrown an error"); + } catch (error: any) { + expect(typeof error.customValue).toBe("number"); + expect(typeof error.customCount).toBe("number"); + expect(error.customValue).toBeCloseTo(3.14); + expect(error.customCount).toBe(42); + } + }); +}); diff --git a/__tests__/exception-types.test.ts b/__tests__/exception-types.test.ts new file mode 100644 index 0000000..0122ff3 --- /dev/null +++ b/__tests__/exception-types.test.ts @@ -0,0 +1,41 @@ +/** + * Exception Type Verification Test + * + * Tests that C++ exceptions are converted to proper JavaScript exception types + * from src/exceptions.ts + */ + +import { WasmManager } from "../src/_wasm"; +import { + SolverRuntimeError, + ZeroFindingError, + OutOfRangeError, + InterceptionError, +} from "../src/exceptions"; + +describe("Exception Type Conversion", () => { + test("globalThis should have exception classes registered", () => { + expect((globalThis as any).SolverRuntimeError).toBe(SolverRuntimeError); + expect((globalThis as any).ZeroFindingError).toBe(ZeroFindingError); + expect((globalThis as any).OutOfRangeError).toBe(OutOfRangeError); + expect((globalThis as any).InterceptionError).toBe(InterceptionError); + }); + + test("C++ SolverRuntimeError should become JS SolverRuntimeError", async () => { + const bclibc = await WasmManager.init(); + + try { + (bclibc as any).testThrowSolverError("Test solver error"); + fail("Should have thrown an error"); + } catch (error: any) { + console.log("Error name:", error.name); + console.log("Error constructor:", error.constructor.name); + console.log("Is Error:", error instanceof Error); + console.log("Is SolverRuntimeError:", error instanceof SolverRuntimeError); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(SolverRuntimeError); + expect(error.name).toBe("SolverRuntimeError"); + } + }); +}); diff --git a/__tests__/incomplete-shot.test.ts b/__tests__/incomplete-shot.test.ts index fa092a1..596ea96 100644 --- a/__tests__/incomplete-shot.test.ts +++ b/__tests__/incomplete-shot.test.ts @@ -1,257 +1,266 @@ import { - Calculator, HitResult, Ammo, UNew, DragModel, - Shot, - Table, + DragTables, Distance, Weapon, Wind, - EulerIntegrationEngine, - RK4IntegrationEngine, - Angular, - Temperature, - Velocity, + IntegrationMethod, + WasmManager, } from "../src"; // Assuming these are in '../src' -import { TrajectoryRangeError, ZeroFindingError } from "../src/exceptions"; // Assuming exceptions are here import { expect, describe, test, beforeEach } from "@jest/globals"; +import { Shot } from "../src/shot"; +import { Calculator } from "../src/interface"; +import { TrajFlag } from "../src/_wasm"; // --- Helper Functions Mimicking Python Fixtures --- // Helper to create a shot with a relative angle in degrees +// Matches Python's create_5_56_mm_shot() fixture const shotWithRelativeAngleInDegrees = (angleInDegrees: number): Shot => { + // 5.56x45mm NATO SS109 const dm = new DragModel({ - bc: 0.223, - dragTable: Table.G7, - weight: 168, - diameter: 0.308, - length: UNew.Inch(1.282), + bc: 0.151, // Match Python + dragTable: DragTables.G7, + weight: 62, // grains, match Python + diameter: 5.56 / 25.4, // Convert 5.56mm to inches + length: 21.0 / 25.4, // Convert 21.0mm to inches }); const ammo = new Ammo({ dm: dm, - mv: UNew.FPS(2750), + mv: 900 * 3.28084, // Convert 900 m/s to fps powderTemp: UNew.Celsius(15), }); - ammo.calcPowderSens(2723, 0); const currentWinds = [new Wind({ velocity: 2, directionFrom: 90 })]; const shot = new Shot({ - weapon: new Weapon({ sightHeight: UNew.Inch(0) }), // Assuming zero sight height for this fixture + weapon: new Weapon({ sightHeight: UNew.Inch(0) }), ammo: ammo, winds: currentWinds, - lookAngle: UNew.Degree(angleInDegrees), // Set the look angle directly + relativeAngle: UNew.Degree(angleInDegrees), // Use relativeAngle to match Python's shot.relative_angle }); return shot; }; // Define the engine types to test with for describe.each -const calculatorsToTest = [ - { engine: EulerIntegrationEngine, name: "EulerIntegrationEngine" }, - { engine: RK4IntegrationEngine, name: "RK4IntegrationEngine" }, +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, ]; // --- Test Suite: Incomplete Shots (Parameterized by Engine) --- // Use describe.each to iterate over different engine implementations -describe.each(calculatorsToTest)("Test Incomplete Shots with %s", ({ engine, name }) => { - let zeroHeightCalc: Calculator; // Declare with 'let' to be reassigned in beforeEach +describe.each(methods)("Test Incomplete Shots with $name", (obj) => { + const { method } = obj; + // Match Python's zero_height_calc fixture with cMinimumVelocity=0.0 + const zeroHeightCalc = new Calculator({ + method, + config: { + minimumVelocity: 0.0, + minimumAltitude: 0.0, + maximumDrop: 0.0, + }, + }); // This beforeEach hook runs before each 'test' function in this describe block. - beforeEach(() => { - zeroHeightCalc = new Calculator({ engine }); // Initialize for each test - }); + beforeEach(async () => {}); - test("test_shot_incomplete", () => { - const angleInDegrees = 5.219710693607955; - const distance = 6937.3716148080375; + test("test_shot_incomplete", async () => { + const angleInDegrees = 5.0; + const distance = UNew.Foot(6500.0); const shot = shotWithRelativeAngleInDegrees(angleInDegrees); - const range = UNew.Meter(distance); const checkEndPoint = (hitResult: HitResult) => { - const lastPoint = hitResult.trajectory[hitResult.trajectory.length - 1]; - const lastPointDistance = lastPoint.distance.In(Distance.Meter); - const lastPointHeight = lastPoint.height.In(Distance.Meter); - // console.log(`lastPointDistance=${lastPointDistance} lastPointHeight=${lastPointHeight}`); + console.log(`=== Trajectory points (${hitResult.trajectory.length} total) ===`); + hitResult.trajectory.forEach((point, i) => { + console.log( + `${i}: distance=${point.distance.foot.toFixed(2)}ft, height=${point.height.foot.toFixed(2)}ft` + ); + }); + console.log(`Error: ${hitResult.error?.message || "none"}`); - // Reverted to Python's expected value for lastPointDistance - expect(lastPointDistance).toBeGreaterThan(3525.0); + const lastPoint = hitResult.trajectory[hitResult.trajectory.length - 1]; + const lastPointDistance = lastPoint.distance.In(Distance.Foot); + const lastPointHeight = lastPoint.height.In(Distance.Foot); - expect(lastPointHeight).toBeCloseTo(0, 10); // abs=1e-10 is very small, use higher precision + expect(lastPointDistance).toBeGreaterThan(6416.0); + expect(lastPointHeight).toBeLessThan(1e-9); // Basically zero }; let hitResult: HitResult; - let extraData: boolean; - - // Case 1: extra_data = false - extraData = false; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData }); - } catch (e: any) { - console.log(`Caught error in test_shot_incomplete (Case 1): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; // Re-throw if it's an unexpected error - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty + let trajFlags: TrajFlag; + + // Case 1: flags = NONE + trajFlags = TrajFlag.NONE; + hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: distance, + filterFlags: trajFlags, + raiseRangeError: false, + }); checkEndPoint(hitResult); - // Case 2: extra_data = false, trajectory_step = range (single point) - extraData = false; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData, trajectoryStep: range }); - } catch (e: any) { - console.log(`Caught error in test_shot_incomplete (Case 2): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty + // Case 2: flags = NONE, trajectory_step = distance (single point) + trajFlags = TrajFlag.NONE; + hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: distance, + filterFlags: trajFlags, + trajectoryStep: distance, + raiseRangeError: false, + }); checkEndPoint(hitResult); - // Case 3: extra_data = true - extraData = true; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData }); - } catch (e: any) { - console.log(`Caught error in test_shot_incomplete (Case 3): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty + // Case 3: flags = ALL + trajFlags = TrajFlag.ALL; + hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: distance, + filterFlags: trajFlags, + raiseRangeError: false, + }); checkEndPoint(hitResult); - // Case 4: extra_data = true, trajectory_step = range (single point) - extraData = true; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData, trajectoryStep: range }); - } catch (e: any) { - console.log(`Caught error in test_shot_incomplete (Case 4): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty + // Case 4: flags = ALL, trajectory_step = distance (single point) + trajFlags = TrajFlag.ALL; + hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: distance, + filterFlags: trajFlags, + trajectoryStep: distance, + raiseRangeError: false, + }); checkEndPoint(hitResult); }); - test("test_vertical_shot", () => { + test("test_vertical_shot", async () => { const shot = shotWithRelativeAngleInDegrees(90); // Vertical shot const range = UNew.Meter(10); // A small range let hitResult: HitResult; - let extraData: boolean; - - // Case 1: extra_data = false - extraData = false; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData }); - } catch (e: any) { - console.log(`Caught error in test_vertical_shot (Case 1): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty - const lastPointFalse = hitResult.trajectory[hitResult.trajectory.length - 1]; - expect(lastPointFalse.distance.In(Distance.Meter)).toBeCloseTo(0, 10); // abs=1e-10 - expect(lastPointFalse.height.In(Distance.Meter)).toBeCloseTo(0, 1); // abs=0.1 - - // Case 2: extra_data = true - extraData = true; - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData }); - } catch (e: any) { - console.log(`Caught error in test_vertical_shot (Case 2): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty - const lastPointTrue = hitResult.trajectory[hitResult.trajectory.length - 1]; - expect(lastPointTrue.distance.In(Distance.Meter)).toBeCloseTo(0, 10); // abs=1e-10 - expect(lastPointTrue.height.In(Distance.Meter)).toBeCloseTo(0, 1); // abs=0.1 + + // Case 1: Without flags - should have exactly 2 points + hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: range, + raiseRangeError: false, + }); + + // Debug output + console.log("=== Vertical Shot Debug ==="); + console.log("Trajectory length:", hitResult.trajectory.length); + console.log("Error:", hitResult.error); + hitResult.trajectory.forEach((point, i) => { + console.log( + `Point ${i}: dist=${point.distance.foot.toFixed(2)}ft, height=${point.height.foot.toFixed(2)}ft, time=${point.time.toFixed(3)}s` + ); + }); + + expect(hitResult.trajectory.length).toBe(2); + expect(hitResult.trajectory[hitResult.trajectory.length - 1].height.rawValue).toBeLessThan( + 1e-9 + ); + + // Case 2: With ALL flags and config to allow crossing zero + const calcWithConfig = new Calculator({ + method, + config: { + minimumVelocity: 0.0, + minimumAltitude: -1.0, + maximumDrop: -1.0, + }, + }); + hitResult = await calcWithConfig.fire({ + shot, + trajectoryRange: range, + filterFlags: TrajFlag.ALL, + raiseRangeError: false, + }); + + const zeroDown = hitResult.flag(TrajFlag.ZERO_DOWN); + expect(zeroDown).not.toBeNull(); + expect(zeroDown!.distance.rawValue).toBeCloseTo(0, 10); + expect(zeroDown!.height.In(Distance.Meter)).toBeCloseTo(0, 6); + + // Don't duplicate points + expect(hitResult.trajectory[hitResult.trajectory.length - 1].time).not.toBe( + hitResult.trajectory[hitResult.trajectory.length - 2].time + ); }); - test("test_no_duplicate_points", () => { - // This is a shot for point (1000, 0) - const shot = shotWithRelativeAngleInDegrees(0.46571949074059704); + test("test_no_duplicate_points", async () => { + // This is a shot for point (1000ft, 0) + const shot = shotWithRelativeAngleInDegrees(0.1385398904676405); + const zeroDistance = UNew.Foot(1000); // Setting up bigger distance than required by shot - const range = UNew.Meter(1100); + const range = UNew.Foot(1100); + + const calcWithConfig = new Calculator({ + method, + config: { + minimumVelocity: 0.0, + minimumAltitude: -10.0, + maximumDrop: -10.0, + }, + }); - let hitResult: HitResult; - let extraData = false; // Based on Python test - - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData, trajectoryStep: UNew.Meter(100) }); - } catch (e: any) { - console.log(`Caught error in test_no_duplicate_points: ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } - expect(hitResult.trajectory.length).toBeGreaterThan(0); // Ensure trajectory is not empty + const hitResult = await calcWithConfig.fire({ + shot, + trajectoryRange: range, + trajectoryStep: UNew.Foot(100), + raiseRangeError: false, + }); expect(hitResult.trajectory.length).toBeGreaterThanOrEqual(2); - // Jest's expect().not.toBe() compares by reference. We need to compare properties. - // The original Python test uses `assert hit_result[-2] != hit_result[-1]`, - // which for dataclasses compares values. For JS objects, need to compare content. - expect(JSON.stringify(hitResult.trajectory[hitResult.trajectory.length - 2].inDefUnits())).not.toEqual( - JSON.stringify(hitResult.trajectory[hitResult.trajectory.length - 1].inDefUnits()) + expect(hitResult.trajectory[hitResult.trajectory.length - 2]).not.toEqual( + hitResult.trajectory[hitResult.trajectory.length - 1] + ); + + const resultAtZero = await hitResult.getAt( + (await WasmManager.init())._TrajectoryDataInterpKey.DISTANCE, + zeroDistance.foot ); + expect(resultAtZero).not.toBeNull(); + expect(resultAtZero.distance.In(Distance.Foot)).toBeCloseTo(1000, 1); + expect(resultAtZero.height.In(Distance.Foot)).toBeCloseTo(0, 2); const secondLastPoint = hitResult.trajectory[hitResult.trajectory.length - 2]; const lastPoint = hitResult.trajectory[hitResult.trajectory.length - 1]; - expect(secondLastPoint.distance.In(Distance.Meter)).toBeCloseTo(1000, 1); // abs=0.2 in Python is 1 decimal place here - // Reverted to Python's expected height. This test will likely fail now. - expect(secondLastPoint.height.In(Distance.Meter)).toBeCloseTo(0, 2); // abs=0.01 in Python is 2 decimal places here - - expect(lastPoint.distance.In(Distance.Meter)).toBeGreaterThan(secondLastPoint.distance.In(Distance.Meter)); - expect(lastPoint.height.In(Distance.Meter)).toBeLessThan(secondLastPoint.height.In(Distance.Meter)); + expect(lastPoint.distance.In(Distance.Foot)).toBeGreaterThan( + secondLastPoint.distance.In(Distance.Foot) + ); + expect(lastPoint.height.In(Distance.Foot)).toBeLessThan( + secondLastPoint.height.In(Distance.Foot) + ); }); - test("test_no_duplicated_point_many_trajectories", () => { + test("test_no_duplicated_point_many_trajectories", async () => { // Bigger than max range of weapon const range = UNew.Meter(8000); - - for (const extraData of [false, true]) { + const bclibc = await WasmManager.init(); + for (const filterFlags of [TrajFlag.RANGE, TrajFlag.ALL]) { for (let angle = 0; angle <= 90; angle += 10) { const shot = shotWithRelativeAngleInDegrees(angle); - let hitResult: HitResult; - - try { - hitResult = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraData }); - } catch (e: any) { - console.log(`Caught error in test_no_duplicated_point_many_trajectories for angle ${angle} (extraData=${extraData}): ${e.reason || e.message}`); - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResult = new HitResult(shot, e.incompleteTrajectory, extraData); - } else { - throw e; - } - } + + const hitResult = await zeroHeightCalc.fire({ + shot, + trajectoryRange: range, + filterFlags: filterFlags, + raiseRangeError: false, + }); + expect(hitResult.trajectory.length).toBeGreaterThanOrEqual(0); // Ensure trajectory is not null/undefined/empty on error // console.log(`len(hitResult.trajectory)=${hitResult.trajectory.length}`); // In JS, converting array to Set removes duplicates if elements are primitive. // For objects, it removes if they are the exact same reference. // To check for duplicate data, we need to convert to a comparable primitive or string. - const uniquePoints = new Set(hitResult.trajectory.map(p => JSON.stringify(p.inDefUnits()))); + const uniquePoints = new Set( + hitResult.trajectory.map((p) => JSON.stringify(p.inDefUnits())) + ); // Python's test `assert len(hit_result.trajectory)==len(set(hit_result.trajectory))` // This implicitly means if there's an incomplete trajectory, its points are unique. expect(hitResult.trajectory.length).toBe(uniquePoints.size); @@ -277,68 +286,71 @@ describe.each(calculatorsToTest)("Test Incomplete Shots with %s", ({ engine, nam [7126.0478000569165, 0.001, 38.58299087491584], ]; - describe.each(testPoints)("test_end_points_are_included (Distance: %f, Height: %f, Angle: %f)", (distance, height, angleInDegrees) => { - test("should get the same result with and without extra data", () => { - const shot = shotWithRelativeAngleInDegrees(angleInDegrees); - const range = UNew.Meter(distance); - console.log(`\nDistance: ${distance.toFixed(2)} Height: ${height.toFixed(2)}`); - - let hitResultExtraData: HitResult; - let hitResultNoExtraData: HitResult; - - // Test with extra_data = true - const extraDataFlag = true; - try { - hitResultExtraData = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: extraDataFlag }); - } catch (e: any) { - console.log(`Caught error in test_end_points_are_included (extraData=true): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResultExtraData = new HitResult(shot, e.incompleteTrajectory, extraDataFlag); - } else { - throw e; + describe.each(testPoints)( + "test_end_points_are_included (Distance: %f, Height: %f, Angle: %f)", + (distance, height, angleInDegrees) => { + test("should get the same result with and without extra data", async () => { + const shot = shotWithRelativeAngleInDegrees(angleInDegrees); + const range = UNew.Meter(distance); + console.log(`\nDistance: ${distance.toFixed(2)} Height: ${height.toFixed(2)}`); + + let hitResultExtraData: HitResult; + let hitResultNoExtraData: HitResult; + + // Test with extra_data = true + const extraDataFlag = TrajFlag.ALL; + hitResultExtraData = await zeroHeightCalc.fire({ + shot, + trajectoryRange: range, + filterFlags: extraDataFlag, + raiseRangeError: false, + }); + // Ensure trajectory is not empty before accessing elements. + expect(hitResultExtraData.trajectory.length).toBeGreaterThanOrEqual(0); // Can be 0 if error at start + let distanceExtraData = 0; + let heightExtraData = 0; + if (hitResultExtraData.trajectory.length > 0) { + const lastPointExtraData = + hitResultExtraData.trajectory[hitResultExtraData.trajectory.length - 1]; + distanceExtraData = lastPointExtraData.distance.In(Distance.Meter); + heightExtraData = lastPointExtraData.height.In(Distance.Meter); } - } - // Ensure trajectory is not empty before accessing elements. - expect(hitResultExtraData.trajectory.length).toBeGreaterThanOrEqual(0); // Can be 0 if error at start - let distanceExtraData = 0; - let heightExtraData = 0; - if (hitResultExtraData.trajectory.length > 0) { - const lastPointExtraData = hitResultExtraData.trajectory[hitResultExtraData.trajectory.length - 1]; - distanceExtraData = lastPointExtraData.distance.In(Distance.Meter); - heightExtraData = lastPointExtraData.height.In(Distance.Meter); - } - console.log(`extra_data=${extraDataFlag} len(hitResultExtraData.trajectory)=${hitResultExtraData.trajectory.length} Distance ${distanceExtraData.toFixed(2)} Height ${heightExtraData.toFixed(2)}`); - - // Test with extra_data = false - const noExtraDataFlag = false; - try { - hitResultNoExtraData = zeroHeightCalc.fire({ shot, trajectoryRange: range, extraData: noExtraDataFlag }); - } catch (e: any) { - console.log(`Caught error in test_end_points_are_included (extraData=false): ${e.reason || e.message}`); // Added logging - if (e instanceof TrajectoryRangeError && [TrajectoryRangeError.MaximumDropReached, TrajectoryRangeError.MinimumAltitudeReached].includes(e.reason)) { - hitResultNoExtraData = new HitResult(shot, e.incompleteTrajectory, noExtraDataFlag); - } else { - throw e; + console.log( + `extra_data=${extraDataFlag} len(hitResultExtraData.trajectory)=${hitResultExtraData.trajectory.length} Distance ${distanceExtraData.toFixed(2)} Height ${heightExtraData.toFixed(2)}` + ); + + // Test with extra_data = false + const noExtraDataFlag = TrajFlag.RANGE; + hitResultNoExtraData = await zeroHeightCalc.fire({ + shot, + trajectoryRange: range, + filterFlags: noExtraDataFlag, + raiseRangeError: false, + }); + // Ensure trajectory is not empty before accessing elements. + expect(hitResultNoExtraData.trajectory.length).toBeGreaterThanOrEqual(0); // Can be 0 if error at start + let distanceNoExtraData = 0; + let heightNoExtraData = 0; + if (hitResultNoExtraData.trajectory.length > 0) { + const lastPointNoExtraData = + hitResultNoExtraData.trajectory[hitResultNoExtraData.trajectory.length - 1]; + distanceNoExtraData = lastPointNoExtraData.distance.In(Distance.Meter); + heightNoExtraData = lastPointNoExtraData.height.In(Distance.Meter); } - } - // Ensure trajectory is not empty before accessing elements. - expect(hitResultNoExtraData.trajectory.length).toBeGreaterThanOrEqual(0); // Can be 0 if error at start - let distanceNoExtraData = 0; - let heightNoExtraData = 0; - if (hitResultNoExtraData.trajectory.length > 0) { - const lastPointNoExtraData = hitResultNoExtraData.trajectory[hitResultNoExtraData.trajectory.length - 1]; - distanceNoExtraData = lastPointNoExtraData.distance.In(Distance.Meter); - heightNoExtraData = lastPointNoExtraData.height.In(Distance.Meter); - } - console.log(`extra_data=${noExtraDataFlag} len(hitResultNoExtraData.trajectory)=${hitResultNoExtraData.trajectory.length} Distance ${distanceNoExtraData.toFixed(2)} Height ${heightNoExtraData.toFixed(2)}`); - - const distanceDifference = Math.abs(distanceExtraData - distanceNoExtraData); - const heightDifference = Math.abs(heightExtraData - heightNoExtraData); - console.log(`Difference in results Distance: ${distanceDifference.toFixed(2)} Height ${heightDifference.toFixed(2)}`); - - // Reverted to Python's expected tolerance for distanceDifference. - // This test will likely fail for the 89.273... angle if JS still diverges significantly. - expect(distanceDifference).toBeLessThanOrEqual(UNew.Foot(0.2).In(Distance.Meter)); - }); - }); + console.log( + `extra_data=${noExtraDataFlag} len(hitResultNoExtraData.trajectory)=${hitResultNoExtraData.trajectory.length} Distance ${distanceNoExtraData.toFixed(2)} Height ${heightNoExtraData.toFixed(2)}` + ); + + const distanceDifference = Math.abs(distanceExtraData - distanceNoExtraData); + const heightDifference = Math.abs(heightExtraData - heightNoExtraData); + console.log( + `Difference in results Distance: ${distanceDifference.toFixed(2)} Height ${heightDifference.toFixed(2)}` + ); + + // Reverted to Python's expected tolerance for distanceDifference. + // This test will likely fail for the 89.273... angle if JS still diverges significantly. + expect(distanceDifference).toBeLessThanOrEqual(UNew.Foot(0.2).In(Distance.Meter)); + }); + } + ); }); diff --git a/__tests__/interp.test.js b/__tests__/interp.test.js new file mode 100644 index 0000000..ffc8c75 --- /dev/null +++ b/__tests__/interp.test.js @@ -0,0 +1,125 @@ +// tests/wasm/interp.test.js + +import { WasmManager } from "../src"; + +describe("BCLIBC Interpolation Tests (WASM)", () => { + let BCLIBC; + + beforeAll(async () => { + BCLIBC = await WasmManager.init(); + }); + + // ========================================================================= + // Enums + // ========================================================================= + describe("Enums", () => { + test("InterpMethod values", () => { + expect(BCLIBC._InterpMethod.PCHIP).toBe(0); + expect(BCLIBC._InterpMethod.LINEAR).toBe(1); + }); + }); + + // ========================================================================= + // Linear interpolation (2pt) + // ========================================================================= + describe("Linear interpolation (2pt)", () => { + test("success case", () => { + const r = BCLIBC.interpolate2pt(5, 0, 0, 10, 100); + expect(r).toBe(50); + }); + + test("left boundary", () => { + const r = BCLIBC.interpolate2pt(0, 0, 0, 10, 100); + expect(r).toBe(0); + }); + + test("right boundary", () => { + const r = BCLIBC.interpolate2pt(10, 0, 0, 10, 100); + expect(r).toBe(100); + }); + + test("zero division", () => { + expect(() => BCLIBC.interpolate2pt(5, 10, 0, 10, 100)).toThrow(); + }); + }); + + // ========================================================================= + // 3-point PCHIP interpolation + // ========================================================================= + describe("PCHIP interpolation (3pt)", () => { + test("first segment", () => { + const v = BCLIBC.interpolate3pt(2.5, 0, 5, 10, 0, 25, 100); + expect(v).toBeGreaterThan(0); + expect(v).toBeLessThan(25); + }); + + test("second segment", () => { + const v = BCLIBC.interpolate3pt(7.5, 0, 5, 10, 0, 25, 100); + expect(v).toBeGreaterThan(25); + expect(v).toBeLessThan(100); + }); + + test("middle point", () => { + const v = BCLIBC.interpolate3pt(5, 0, 5, 10, 0, 25, 100); + expect(v).toBeCloseTo(25, 2); + }); + }); + + // ========================================================================= + // Hermite interpolation + // ========================================================================= + describe("Hermite interpolation", () => { + test("linear case (slopes = 1)", () => { + const v = BCLIBC.hermite(0.5, 0, 1, 0, 1, 1, 1); + expect(v).toBeCloseTo(0.5, 2); + }); + + test("flat slopes (S-curve)", () => { + const v = BCLIBC.hermite(0.5, 0, 1, 0, 1, 0, 0); + expect(v).toBeGreaterThan(0); + expect(v).toBeLessThan(1); + }); + + test("left boundary", () => { + const v = BCLIBC.hermite(0, 0, 1, 0, 1, 0.5, 0.5); + expect(v).toBeCloseTo(0, 5); + }); + + test("right boundary", () => { + const v = BCLIBC.hermite(1, 0, 1, 0, 1, 0.5, 0.5); + expect(v).toBeCloseTo(1, 5); + }); + }); + + // ========================================================================= + // Real-world ballistics example + // ========================================================================= + describe("Ballistics example", () => { + test("velocity at 250m", () => { + const v = BCLIBC.interpolate3pt(250, 0, 500, 1000, 850, 800, 750); + expect(v).toBeGreaterThan(800); + expect(v).toBeLessThan(850); + }); + + test("velocity at 750m", () => { + const v = BCLIBC.interpolate3pt(750, 0, 500, 1000, 850, 800, 750); + expect(v).toBeGreaterThan(750); + expect(v).toBeLessThan(800); + }); + }); + + // ========================================================================= + // Performance (smoke only, no asserts on time) + // ========================================================================= + describe("Performance (smoke)", () => { + test("interpolation calls do not throw", () => { + expect(() => { + for (let i = 0; i < 10_000; i++) { + BCLIBC.interpolate2pt(5, 0, 0, 10, 100); + BCLIBC.interpolate3pt(2.5, 0, 5, 10, 0, 25, 100); + BCLIBC.hermite(0.5, 0, 1, 0, 1, 0, 0); + } + }).not.toThrow(); + }); + }); +}); diff --git a/__tests__/mbc.test.ts b/__tests__/mbc.test.ts index b0d4a9f..f55a9ed 100644 --- a/__tests__/mbc.test.ts +++ b/__tests__/mbc.test.ts @@ -1,125 +1,128 @@ -import { expect, describe, test, beforeEach } from "@jest/globals"; +import { expect, describe, test } from "@jest/globals"; import { - Calculator, UNew, DragModel, - Table, + DragTables, Ammo, Weapon, - Shot, DragModelMultiBC, BCPoint, - RK4IntegrationEngine, - EulerIntegrationEngine, + IntegrationMethod, + TrajectoryData, } from "../src"; +import { Calculator } from "../src/interface"; +import { Shot } from "../src/shot"; -const calculators = [ - { engine: EulerIntegrationEngine }, // Assuming these are string identifiers or actual classes - { engine: RK4IntegrationEngine }, +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, ]; -describe.each(calculators)("TestMultiBC %s", ({ engine }) => { +describe.each(methods)("TestMultiBC $name", (obj) => { + const { method } = obj; let range = 1000; let step = 100; - let dm = new DragModel({ bc: 0.22, dragTable: Table.G7 }); + let dm = new DragModel({ bc: 0.22, dragTable: DragTables.G7 }); let ammo = new Ammo({ dm: dm, mv: UNew.FPS(2600) }); let weapon = new Weapon({ sightHeight: 4, twist: 12 }); - let calc = new Calculator({ engine }); + let calc = new Calculator({ method }); let baseLineShot = new Shot({ weapon: weapon, ammo: ammo }); - let baselineTrajectory = calc.fire({ - shot: baseLineShot, - trajectoryRange: range, - trajectoryStep: step, - }).trajectory; + let baselineTrajectory: TrajectoryData[]; - beforeEach(() => {}); + beforeAll(async () => { + const hit = await calc.fire({ + shot: baseLineShot, + trajectoryRange: range, + trajectoryStep: step, + }); + baselineTrajectory = hit.trajectory; + }); - test("mbc1", () => { - let dmMulti = DragModelMultiBC({ + test("mbc1", async () => { + const dmMulti = DragModelMultiBC({ bcPoints: [ new BCPoint({ BC: 0.22, V: UNew.FPS(2500) }), new BCPoint({ BC: 0.22, V: UNew.FPS(1500) }), new BCPoint({ BC: 0.22, Mach: 3 }), ], - dragTable: Table.G7, + dragTable: DragTables.G7, }); - let multiShot = new Shot({ + const multiShot = new Shot({ weapon: weapon, ammo: new Ammo({ dm: dmMulti, mv: ammo.mv }), }); - let multiTrajectory = calc.fire({ + const hit = await calc.fire({ shot: multiShot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const multiTrajectory = hit.trajectory; for (let i = 0; i < multiTrajectory.length; i++) { - expect(multiTrajectory[i].formatted()).toEqual( - baselineTrajectory[i].formatted(), - ); + expect(multiTrajectory[i].formatted()).toEqual(baselineTrajectory[i].formatted()); } }); - test("mbc2", () => { - let dmMulti = DragModelMultiBC({ + test("mbc2", async () => { + const dmMulti = DragModelMultiBC({ bcPoints: [ new BCPoint({ BC: 0.22, V: UNew.FPS(2700) }), new BCPoint({ BC: 0.5, V: UNew.FPS(3500) }), ], - dragTable: Table.G7, + dragTable: DragTables.G7, }); - let multiShot = new Shot({ + const multiShot = new Shot({ weapon: weapon, ammo: new Ammo({ dm: dmMulti, mv: ammo.mv }), }); - let multiTrajectory = calc.fire({ + const hit = await calc.fire({ shot: multiShot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const multiTrajectory = hit.trajectory; for (let i = 0; i < multiTrajectory.length; i++) { - expect(multiTrajectory[i].formatted()).toEqual( - baselineTrajectory[i].formatted(), - ); + expect(multiTrajectory[i].formatted()).toEqual(baselineTrajectory[i].formatted()); } }); - test("mbc3", () => { - let dmMulti = DragModelMultiBC({ + test("mbc3", async () => { + const dmMulti = DragModelMultiBC({ bcPoints: [ new BCPoint({ BC: 0.5, V: baselineTrajectory[3].velocity }), new BCPoint({ BC: 0.22, V: baselineTrajectory[2].velocity }), ], - dragTable: Table.G7, + dragTable: DragTables.G7, }); - let multiShot = new Shot({ + const multiShot = new Shot({ weapon: weapon, ammo: new Ammo({ dm: dmMulti, mv: ammo.mv }), }); - let multiTrajectory = calc.fire({ + const hit = await calc.fire({ shot: multiShot, trajectoryRange: range, trajectoryStep: step, - }).trajectory; + }); + const multiTrajectory = hit.trajectory; expect(multiTrajectory[1].velocity.rawValue).toBeCloseTo( baselineTrajectory[1].velocity.rawValue, - 1e-5, + 1e-5 ); expect(multiTrajectory[4].velocity.rawValue).toBeGreaterThan( - baselineTrajectory[4].velocity.rawValue, + baselineTrajectory[4].velocity.rawValue ); }); test("mbc", () => { - let dmMulti = DragModelMultiBC({ + const dmMulti = DragModelMultiBC({ bcPoints: [ new BCPoint({ BC: 0.275, V: UNew.MPS(800) }), new BCPoint({ BC: 0.255, V: UNew.MPS(500) }), new BCPoint({ BC: 0.26, V: UNew.MPS(700) }), ], - dragTable: Table.G7, + dragTable: DragTables.G7, weight: 178, diameter: 0.308, }); @@ -127,26 +130,26 @@ describe.each(calculators)("TestMultiBC %s", ({ engine }) => { expect(dmMulti.dragTable[0].CD).toBeCloseTo(0.1259323091692403, 1e-8); expect(dmMulti.dragTable[dmMulti.dragTable.length - 1].CD).toBeCloseTo( 0.1577125859466895, - 1e-8, + 1e-8 ); }); test("valid", () => { - let dmMulti = DragModelMultiBC({ + const dmMulti = DragModelMultiBC({ bcPoints: [ new BCPoint({ BC: 0.417, V: UNew.MPS(745) }), new BCPoint({ BC: 0.409, V: UNew.MPS(662) }), new BCPoint({ BC: 0.4, V: UNew.MPS(580) }), ], - dragTable: Table.G7, + dragTable: DragTables.G7, weight: 285, diameter: 0.338, }); - let cds = dmMulti.dragTable.map((p) => p.CD); - let machs = dmMulti.dragTable.map((p) => p.Mach); + const cds = dmMulti.dragTable.map((p) => p.CD); + const machs = dmMulti.dragTable.map((p) => p.Mach); - let reference = [ + const reference = [ [1, 0.3384895315], [2, 0.2585639866], [3, 0.2069547831], diff --git a/__tests__/trajectory.test.ts b/__tests__/trajectory.test.ts index db9a295..da50030 100644 --- a/__tests__/trajectory.test.ts +++ b/__tests__/trajectory.test.ts @@ -1,322 +1,308 @@ import { describe, expect, test } from "@jest/globals"; +import { Calculator } from "../src/interface"; import { - Calculator, Ammo, Atmo, DragModel, - Table, - Shot, + DragTables, UNew, Unit, Weapon, Wind, - TrajFlag, - EulerIntegrationEngine, - RK4IntegrationEngine, + Angular, + HitResult, + TrajectoryData, } from "../src"; +import { Shot } from "../src/shot"; +import { _TrajectoryRequest } from "../build/bclibc"; +import { IntegrationMethod, TrajFlag } from "../src/_wasm"; -function customAssertEqual(a, b, accuracy, name) { - // test(name, () => { +type TestItem = [ + number, // idx + number, // distance + number, // velocity + number, // mach + number, // energy + number, // path + number, // hold + number, // windage + number, // windAdj + number, // time + number, // ogw + number, // unit +]; + +function customAssertEqual(a: number, b: number, accuracy: number, name: string) { expect(Math.abs(a - b)).toBeLessThan(accuracy); - // }); } -function validateOne( - data, - distance, - velocity, - mach, - energy, - path, - hold, - windage, - wind_adjustment, - time, - ogw, - adjustment_unit, -) { - customAssertEqual(distance, data.distance.In(Unit.Yard), 0.001, "Distance"); - customAssertEqual(velocity, data.velocity.In(Unit.FPS), 5, "Velocity"); - customAssertEqual(mach, data.mach, 0.005, "Mach"); - customAssertEqual(energy, data.energy.In(Unit.FootPound), 5, "Energy"); - customAssertEqual(time, data.time, 0.06, "Time"); - customAssertEqual(ogw, data.ogw.In(Unit.Pound), 1, "OGW"); +function getAccuracy(distance: number, isWindage: boolean): number { + if (distance >= 800) return isWindage ? 1.5 : 4; + if (distance >= 500) return 1; + return 0.5; +} - if (distance >= 800) { - customAssertEqual(path, data.height.In(Unit.Inch), 4, "Height"); - } else if (distance >= 500) { - customAssertEqual(path, data.height.In(Unit.Inch), 1, "Height"); - } else { - customAssertEqual(path, data.height.In(Unit.Inch), 0.5, "Height"); - } +type Validation = { + name: string; + get: (d: TrajectoryData, unit?: number) => number; + expected: (item: TestItem) => number; + accuracy: number | ((item: TestItem) => number); + skip?: (item: TestItem) => boolean; +}; - if (distance > 1) { - customAssertEqual( - hold, - data.dropAdjustment.In(adjustment_unit), - 0.5, - "Hold", - ); - } +const allValidations: Validation[] = [ + { + name: "distance", + get: (d) => d.distance.In(Unit.Yard), + expected: (item) => item[1], + accuracy: 0.001, + }, + { + name: "velocity", + get: (d) => d.velocity.In(Unit.FPS), + expected: (item) => item[2], + accuracy: 5, + }, + { + name: "mach", + get: (d) => d.mach, + expected: (item) => item[3], + accuracy: 0.005, + }, + { + name: "energy", + get: (d) => d.energy.In(Unit.FootPound), + expected: (item) => item[4], + accuracy: 5, + }, + { + name: "time", + get: (d) => d.time, + expected: (item) => item[9], + accuracy: 0.06, + }, + { + name: "ogw", + get: (d) => d.ogw.In(Unit.Pound), + expected: (item) => item[10], + accuracy: 1, + }, + { + name: "height", + get: (d) => d.height.In(Unit.Inch), + expected: (item) => item[5], + accuracy: (item) => getAccuracy(item[1], false), + }, + { + name: "hold", + get: (d, unit) => d.dropAngle.In(unit!), + expected: (item) => item[6], + accuracy: 0.5, + skip: (item) => item[1] <= 1, + }, + { + name: "windage", + get: (d) => d.windage.In(Unit.Inch), + expected: (item) => item[7], + accuracy: (item) => getAccuracy(item[1], true), + }, + { + name: "wind adjust", + get: (d, unit) => d.windageAngle.In(unit!), + expected: (item) => item[8], + accuracy: 0.5, + skip: (item) => item[1] <= 1, + }, +]; - if (distance >= 800) { - customAssertEqual(windage, data.windage.In(Unit.Inch), 1.5, "Windage"); - } else if (distance >= 500) { - customAssertEqual(windage, data.windage.In(Unit.Inch), 1, "Windage"); - } else { - customAssertEqual(windage, data.windage.In(Unit.Inch), 0.5, "Windage"); - } +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, +]; - if (distance > 1) { - customAssertEqual( - wind_adjustment, - data.windageAdjustment.In(adjustment_unit), - 0.5, - "WindageAdjustment", - ); - } +describe.each(methods)("trajectory $name", (obj) => { + const { method } = obj; - expect(data.flag & TrajFlag.RANGE).toBeTruthy(); -} + describe("zero", () => { + test("G1", async () => { + const dm = new DragModel({ + bc: 0.365, + dragTable: DragTables.G1, + weight: 69, + diameter: 0.223, + length: 0.9, + }); + const ammo = new Ammo({ dm: dm, mv: 2600 }); + const weapon = new Weapon({ sightHeight: UNew.Inch(3.2) }); + const atmo = Atmo.icao(); + const calc = new Calculator({ method }); -const calculators = [ - { engine: EulerIntegrationEngine }, // Assuming these are string identifiers or actual classes - { engine: RK4IntegrationEngine }, -]; + const zero_angle: Angular = await calc.barrelElevationForTarget( + new Shot({ weapon, ammo, atmo }), + UNew.Yard(100) + ); -describe.each(calculators)("trajectory %s", ({ engine }) => { - test("zero1", () => { - const dm = new DragModel({ - bc: 0.365, - dragTable: Table.G1, - weight: 69, - diameter: 0.223, - length: 0.9, + expect(zero_angle.In(Unit.Radian)).toBeCloseTo(0.001651, 1e-6); }); - const ammo = new Ammo({ dm: dm, mv: 2600 }); - const weapon = new Weapon({ sightHeight: UNew.Inch(3.2) }); - const atmo = Atmo.icao(); - const calc = new Calculator({ engine }); - const zero_angle = calc.barrelElevationForTarget( - new Shot({ weapon, ammo, atmo }), - UNew.Yard(100), - ); + test("G7", async () => { + const dm = new DragModel({ + bc: 0.223, + dragTable: DragTables.G7, + weight: 69, + diameter: 0.223, + length: 0.9, + }); + const ammo = new Ammo({ dm: dm, mv: UNew.FPS(2750) }); + const weapon = new Weapon({ twist: UNew.Inch(2) }); + const atmo = Atmo.icao(); + const calc = new Calculator({ method }); - expect(zero_angle.In(Unit.Radian)).toBeCloseTo(0.001651, 1e-6); - }); + const zero_angle: Angular = await calc.barrelElevationForTarget( + new Shot({ weapon, ammo, atmo }), + UNew.Yard(100).In(Unit.Foot) + ); - test("zero2", () => { - const dm = new DragModel({ - bc: 0.223, - dragTable: Table.G7, - weight: 69, - diameter: 0.223, - length: 0.9, + expect(zero_angle.In(Unit.Radian)).toBeCloseTo(0.0012286, 1e-6); }); - const ammo = new Ammo({ dm: dm, mv: UNew.FPS(2750) }); - const weapon = new Weapon({ twist: UNew.Inch(2) }); - - const atmo = Atmo.icao(); - const calc = new Calculator({ engine }); + }); - const zero_angle = calc.barrelElevationForTarget( - new Shot({ weapon, ammo, atmo }), - UNew.Yard(100), - ); + describe("path G1", () => { + let tData: TrajectoryData[]; - expect(zero_angle.In(Unit.Radian)).toBeCloseTo(0.0012286, 1e-6); - }); + beforeAll(async () => { + const dm = new DragModel({ + bc: 0.223, + dragTable: DragTables.G1, + weight: 168, + diameter: 0.308, + length: 1.282, + }); + const ammo = new Ammo({ dm: dm, mv: 2750 }); + const weapon = new Weapon({ + sightHeight: UNew.Inch(2), + zeroElevation: UNew.Radian(0.001228), + }); + const atmo = Atmo.icao(); + const shot_info = new Shot({ + weapon, + ammo, + atmo, + winds: [ + new Wind({ + velocity: UNew.MPH(5), + directionFrom: UNew.OClock(10.5), + }), + ], + }); - test("path_g1", () => { - const dm = new DragModel({ - bc: 0.223, - dragTable: Table.G1, - weight: 168, - diameter: 0.308, - length: 1.282, + const calc = new Calculator({ method }); + const hit: HitResult = await calc.fire({ + shot: shot_info, + trajectoryRange: UNew.Yard(1000), + trajectoryStep: UNew.Yard(100), + timeStep: 0.0, + denseOutput: false, + }); + tData = hit.trajectory; }); - const ammo = new Ammo({ dm: dm, mv: 2750 }); - const weapon = new Weapon({ - sightHeight: UNew.Inch(2), - zeroElevation: UNew.Radian(0.001228), - }); - const atmo = Atmo.icao(); - const shot_info = new Shot({ - weapon: weapon, - ammo: ammo, - atmo: atmo, - winds: [ - new Wind({ - velocity: UNew.MPH(5), - directionFrom: UNew.OClock(10.5), - }), - ], + test("length", () => { + expect(tData.length).toEqual(11); }); - const calc = new Calculator({ engine }); - const tData = calc.fire({ - shot: shot_info, - trajectoryRange: UNew.Yard(1000), - trajectoryStep: UNew.Yard(100), - }).trajectory; + const items: TestItem[] = [ + [0, 0, 2750, 2.463, 2820.6, -2, 0, 0, 0, 0, 880, Unit.MOA], + [1, 100, 2351.2, 2.106, 2061, 0, 0, -0.6, -0.6, 0.118, 550, Unit.MOA], + [5, 500, 1169.1, 1.047, 509.8, -87.9, -16.8, -19.5, -3.7, 0.857, 67, Unit.MOA], + [10, 1000, 776.4, 0.695, 224.9, -823.9, -78.7, -87.5, -8.4, 2.495, 20, Unit.MOA], + ]; - expect(tData.length).toEqual(11); + describe.each(items)("index %i", (...item) => { + test.each(allValidations)("$name", (val) => { + if (val.skip && val.skip(item)) return; - const data = [ - [tData[0], 0, 2750, 2.463, 2820.6, -2, 0, 0, 0, 0, 880, Unit.MOA], - [ - tData[1], - 100, - 2351.2, - 2.106, - 2061, - 0, - 0, - -0.6, - -0.6, - 0.118, - 550, - Unit.MOA, - ], - [ - tData[5], - 500, - 1169.1, - 1.047, - 509.8, - -87.9, - -16.8, - -19.5, - -3.7, - 0.857, - 67, - Unit.MOA, - ], - [ - tData[10], - 1000, - 776.4, - 0.695, - 224.9, - -823.9, - -78.7, - -87.5, - -8.4, - 2.495, - 20, - Unit.MOA, - ], - ]; + const data = tData[item[0]]; + const expectedValue = val.expected(item); + const accuracy = + typeof val.accuracy === "function" ? val.accuracy(item) : val.accuracy; - data.forEach((item) => { - // describe(`validateOne ${data.indexOf(item)}`, () => { - validateOne(...item); - // }); + customAssertEqual(expectedValue, val.get(data, item[11]), accuracy, val.name); + }); + + test("flag", () => { + expect(tData[item[0]].flag & TrajFlag.RANGE).toBeTruthy(); + }); }); }); - test("path_g7", () => { - const dm = new DragModel({ - bc: 0.223, - dragTable: Table.G7, - weight: 168, - diameter: 0.308, - length: 1.282, - }); - const ammo = new Ammo({ dm: dm, mv: UNew.FPS(2750) }); - const weapon = new Weapon({ - sightHeight: UNew.Inch(2), - twist: UNew.Inch(12), - zeroElevation: UNew.MOA(4.221), + describe("path G7", () => { + let tData: TrajectoryData[]; + + beforeAll(async () => { + const dm = new DragModel({ + bc: 0.223, + dragTable: DragTables.G7, + weight: 168, + diameter: 0.308, + length: 1.282, + }); + const ammo = new Ammo({ dm: dm, mv: UNew.FPS(2750) }); + const weapon = new Weapon({ + sightHeight: UNew.Inch(2), + twist: UNew.Inch(12), + zeroElevation: UNew.MOA(4.221), + }); + const atmo = Atmo.icao(); + const shot_info = new Shot({ + weapon, + ammo, + atmo, + winds: [ + new Wind({ + velocity: UNew.MPH(5), + directionFrom: UNew.Degree(-45), + }), + ], + }); + + const calc = new Calculator({ method }); + const hit: HitResult = await calc.fire({ + shot: shot_info, + trajectoryRange: UNew.Yard(1000), + trajectoryStep: UNew.Yard(100), + timeStep: 0.0, + denseOutput: false, + }); + tData = hit.trajectory; }); - const atmo = Atmo.icao(); - const shot_info = new Shot({ - weapon: weapon, - ammo: ammo, - atmo: atmo, - winds: [ - new Wind({ - velocity: UNew.MPH(5), - directionFrom: UNew.Degree(-45), - }), - ], + test("length", () => { + expect(tData.length).toEqual(11); }); - const calc = new Calculator({ engine }); - const tData = calc.fire({ - shot: shot_info, - trajectoryRange: UNew.Yard(1000), - trajectoryStep: UNew.Yard(100), - }).trajectory; + const items: TestItem[] = [ + [0, 0, 2750, 2.46, 2821, -2.0, 0.0, 0.0, 0.0, 0.0, 880, Unit.MIL], + [1, 100, 2545, 2.28, 2416, 0.0, 0.0, -0.2, -0.06, 0.113, 698, Unit.MIL], + [5, 500, 1814, 1.62, 1227, -56.2, -3.2, -6.3, -0.36, 0.672, 252, Unit.MIL], + [10, 1000, 1086, 0.97, 440, -399.9, -11.3, -31.6, -0.9, 1.748, 54, Unit.MIL], + ]; - expect(tData.length).toEqual(11); + describe.each(items)("index %i", (...item) => { + test.each(allValidations)("$name", (val) => { + if (val.skip && val.skip(item)) return; - const data = [ - [ - tData[0], - 0, - 2750, - 2.46, - 2821, - -2.0, - 0.0, - 0.0, - 0.0, - 0.0, - 880, - Unit.MIL, - ], - [ - tData[1], - 100, - 2545, - 2.28, - 2416, - 0.0, - 0.0, - -0.2, - -0.06, - 0.113, - 698, - Unit.MIL, - ], - [ - tData[5], - 500, - 1814, - 1.62, - 1227, - -56.2, - -3.2, - -6.3, - -0.36, - 0.672, - 252, - Unit.MIL, - ], - [ - tData[10], - 1000, - 1086, - 0.97, - 440, - -399.9, - -11.3, - -31.6, - -0.9, - 1.748, - 54, - Unit.MIL, - ], - ]; + const data = tData[item[0]]; + const expectedValue = val.expected(item); + const accuracy = + typeof val.accuracy === "function" ? val.accuracy(item) : val.accuracy; + + customAssertEqual(expectedValue, val.get(data, item[11]), accuracy, val.name); + }); - data.forEach((item) => { - // describe(`validateOne ${data.indexOf(item)}`, () => { - validateOne(...item); - // }); + test("flag", () => { + expect(tData[item[0]].flag & TrajFlag.RANGE).toBeTruthy(); + }); }); }); }); diff --git a/__tests__/unit.test.ts b/__tests__/unit.test.ts index edb9908..5b34b01 100644 --- a/__tests__/unit.test.ts +++ b/__tests__/unit.test.ts @@ -1,8 +1,28 @@ import { describe, expect, test } from "@jest/globals"; -import { Measure, UNew, Unit, UnitProps, unitTypeCoerce } from "../src"; +import { + Dimension, + Angular, + Distance, + Energy, + Pressure, + Temperature, + Velocity, + Weight, + UNew, + Unit, + type AngularUnit, + type DistanceUnit, + type VelocityUnit, + type WeightUnit, + type TemperatureUnit, + type PressureUnit, + type EnergyUnit, + UnitProps, + unitTypeCoerce, +} from "../src"; describe("Unit back'n'forth", () => { - function backAndForth(value: number, units: Unit): void { + function backAndForth(value: number, units: AllowedUnitT): void { const u = UNew[units](value); const v = u.In(units); @@ -24,14 +44,7 @@ describe("Unit back'n'forth", () => { } describe("Angular", () => { - backAndForthAll([ - Unit.Degree, - Unit.MOA, - Unit.MRad, - Unit.MIL, - Unit.Radian, - Unit.Thousand, - ]); + backAndForthAll([Unit.Degree, Unit.MOA, Unit.MRad, Unit.MIL, Unit.Radian, Unit.Thousand]); }); describe("Distance", () => { @@ -58,12 +71,7 @@ describe("Unit back'n'forth", () => { }); describe("Temperature", () => { - backAndForthAll([ - Unit.Fahrenheit, - Unit.Kelvin, - Unit.Celsius, - Unit.Rankin, - ]); + backAndForthAll([Unit.Fahrenheit, Unit.Kelvin, Unit.Celsius, Unit.Rankin]); }); describe("Velocity", () => { @@ -83,31 +91,69 @@ describe("Unit back'n'forth", () => { }); describe("Unit coercion", () => { - test("As number", () => { - const unit = unitTypeCoerce(10, Measure.Distance, Unit.Yard); + test("Distance as number", () => { + const unit = unitTypeCoerce(10, Distance, Unit.Yard); expect(unit.In(Unit.Yard)).toBeCloseTo(10, 7); }); - test("As AbstractUnit", () => { - const unit = unitTypeCoerce(UNew.Yard(10), Measure.Distance, Unit.Yard); + test("Distance as instance", () => { + const unit = unitTypeCoerce(UNew.Yard(10), Distance, Unit.Yard); expect(unit.In(Unit.Yard)).toBeCloseTo(10, 7); }); - test("As invalid value", () => { - // @ts-ignore + test("Angular as number", () => { + const unit = unitTypeCoerce(45, Angular, Unit.Degree); + expect(unit.In(Unit.Degree)).toBeCloseTo(45, 7); + }); + + test("Angular as instance", () => { + const unit = unitTypeCoerce(UNew.Degree(45), Angular, Unit.Degree); + expect(unit.In(Unit.Degree)).toBeCloseTo(45, 7); + }); + + test("Velocity as number", () => { + const unit = unitTypeCoerce(2800, Velocity, Unit.FPS); + expect(unit.In(Unit.FPS)).toBeCloseTo(2800, 7); + }); + + test("Weight as instance", () => { + const unit = unitTypeCoerce(UNew.Grain(150), Weight, Unit.Grain); + expect(unit.In(Unit.Grain)).toBeCloseTo(150, 7); + }); + + test("Temperature as number", () => { + const unit = unitTypeCoerce(59, Temperature, Unit.Fahrenheit); + expect(unit.In(Unit.Fahrenheit)).toBeCloseTo(59, 7); + }); + + test("Pressure as instance", () => { + const unit = unitTypeCoerce(UNew.InHg(29.92), Pressure, Unit.InHg); + expect(unit.In(Unit.InHg)).toBeCloseTo(29.92, 7); + }); + + test("Energy as number", () => { + const unit = unitTypeCoerce(2500, Energy, Unit.FootPound); + expect(unit.In(Unit.FootPound)).toBeCloseTo(2500, 7); + }); + + test("Invalid value (string)", () => { + // @ts-expect-error - Testing invalid input expect(() => - unitTypeCoerce("invalid", Measure.Distance, Unit.Yard), - ).toThrowError( - `Instance must be a type of ${Measure.Distance.name} or 'number'`, - ); + unitTypeCoerce("invalid", Distance, Unit.Yard) + ).toThrow(`Instance must be a type of Distance or 'number'`); }); - test("As undefined", () => { - //@ts-ignore + test("Undefined value", () => { + // @ts-expect-error - Testing invalid input expect(() => - unitTypeCoerce(undefined, Measure.Distance, Unit.Yard), - ).toThrowError( - `Instance must be a type of ${Measure.Distance.name} or 'number'`, + unitTypeCoerce(undefined, Distance, Unit.Yard) + ).toThrow(`Instance must be a type of Distance or 'number'`); + }); + + test("Null value", () => { + // @ts-expect-error - Testing invalid input + expect(() => unitTypeCoerce(null, Distance, Unit.Yard)).toThrow( + `Instance must be a type of Distance or 'number'` ); }); }); diff --git a/__tests__/vector.test.ts b/__tests__/vector.test.ts index a811732..247e57e 100644 --- a/__tests__/vector.test.ts +++ b/__tests__/vector.test.ts @@ -15,22 +15,20 @@ describe("VectorJs module", () => { test("Unary", () => { let v1 = new Vector(1, 2, 3); - expect(Math.abs(v1.magnitude() - 3.74165738677)).toBeLessThanOrEqual( - 1e-7, - ); + expect(Math.abs(v1.mag() - 3.74165738677)).toBeLessThanOrEqual(1e-7); - let v2 = v1.negate(); + let v2 = v1.neg(); expect(v2.x).toBe(-1); expect(v2.y).toBe(-2); expect(v2.z).toBe(-3); - v2 = v1.normalize(); + v2 = v1.norm(); expect(v2.x).toBeLessThanOrEqual(1); expect(v2.y).toBeLessThanOrEqual(1); expect(v2.z).toBeLessThanOrEqual(1); v1 = new Vector(0, 0, 0); - v2 = v1.normalize(); + v2 = v1.norm(); expect(v2.x).toBe(0); expect(v2.y).toBe(0); expect(v2.z).toBe(0); @@ -43,15 +41,15 @@ describe("VectorJs module", () => { expect(v2.y).toBe(4); expect(v2.z).toBe(6); - v2 = v1.subtract(v2); + v2 = v1.sub(v2); expect(v2.x).toBe(-1); expect(v2.y).toBe(-2); expect(v2.z).toBe(-3); - let mul = v1.mulByVector(v1.copy()); + let mul = v1.dot(v1.copy()); expect(mul).toBe(1 + 4 + 9); - v2 = v1.mulByConst(3); + v2 = v1.mul(3); expect(v2.x).toBe(3); expect(v2.y).toBe(6); expect(v2.z).toBe(9); diff --git a/__tests__/wasm/vector.test.js b/__tests__/wasm/vector.test.js new file mode 100644 index 0000000..70e2c65 --- /dev/null +++ b/__tests__/wasm/vector.test.js @@ -0,0 +1,469 @@ +// tests/wasm/vector.test.js + +import { WasmManager } from "../../src"; + +describe("BCLIBC Vector Tests", () => { + let BCLIBC; + + beforeAll(async () => { + BCLIBC = await WasmManager.init(); + }); + + describe("Vector Creation", () => { + test("should create vector with specified coordinates", () => { + const v = new BCLIBC._Vector(1, 2, 3); + + expect(v.x).toBe(1); + expect(v.y).toBe(2); + expect(v.z).toBe(3); + + v.delete(); + }); + + test("should create zero vector with default constructor", () => { + const v = new BCLIBC._Vector(); + + expect(v.x).toBe(0); + expect(v.y).toBe(0); + expect(v.z).toBe(0); + + v.delete(); + }); + + test("should allow property modification", () => { + const v = new BCLIBC._Vector(1, 2, 3); + + v.x = 10; + v.y = 20; + v.z = 30; + + expect(v.x).toBe(10); + expect(v.y).toBe(20); + expect(v.z).toBe(30); + + v.delete(); + }); + }); + + describe("Vector Arithmetic", () => { + test("should add two vectors", () => { + const v1 = new BCLIBC._Vector(1, 2, 3); + const v2 = new BCLIBC._Vector(4, 5, 6); + const result = v1.add(v2); + + expect(result.x).toBe(5); + expect(result.y).toBe(7); + expect(result.z).toBe(9); + + v1.delete(); + v2.delete(); + result.delete(); + }); + + test("should subtract two vectors", () => { + const v1 = new BCLIBC._Vector(10, 20, 30); + const v2 = new BCLIBC._Vector(1, 2, 3); + const result = v1.sub(v2); + + expect(result.x).toBe(9); + expect(result.y).toBe(18); + expect(result.z).toBe(27); + + v1.delete(); + v2.delete(); + result.delete(); + }); + + test("should negate vector", () => { + const v = new BCLIBC._Vector(1, -2, 3); + const result = v.neg(); + + expect(result.x).toBe(-1); + expect(result.y).toBe(2); + expect(result.z).toBe(-3); + + v.delete(); + result.delete(); + }); + + test("should multiply vector by scalar", () => { + const v = new BCLIBC._Vector(1, 2, 3); + const result = v.mul(2); + + expect(result.x).toBe(2); + expect(result.y).toBe(4); + expect(result.z).toBe(6); + + v.delete(); + result.delete(); + }); + + test("should divide vector by scalar", () => { + const v = new BCLIBC._Vector(10, 20, 30); + const result = v.div(10); + + expect(result.x).toBe(1); + expect(result.y).toBe(2); + expect(result.z).toBe(3); + + v.delete(); + result.delete(); + }); + + test("should calculate dot product", () => { + const v1 = new BCLIBC._Vector(1, 2, 3); + const v2 = new BCLIBC._Vector(4, 5, 6); + const dot = v1.dot(v2); + + // 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32 + expect(dot).toBe(32); + + v1.delete(); + v2.delete(); + }); + + test("dot product of perpendicular vectors should be zero", () => { + const v1 = new BCLIBC._Vector(1, 0, 0); + const v2 = new BCLIBC._Vector(0, 1, 0); + const dot = v1.dot(v2); + + expect(dot).toBe(0); + + v1.delete(); + v2.delete(); + }); + }); + + describe("In-Place Operations", () => { + test("should add in-place", () => { + const v1 = new BCLIBC._Vector(1, 2, 3); + const v2 = new BCLIBC._Vector(4, 5, 6); + + const result = v1.iadd(v2); + + expect(v1.x).toBe(5); + expect(v1.y).toBe(7); + expect(v1.z).toBe(9); + expect(result).toBe(v1); // Returns this + + v1.delete(); + v2.delete(); + }); + + test("should subtract in-place", () => { + const v1 = new BCLIBC._Vector(10, 20, 30); + const v2 = new BCLIBC._Vector(1, 2, 3); + + v1.isub(v2); + + expect(v1.x).toBe(9); + expect(v1.y).toBe(18); + expect(v1.z).toBe(27); + + v1.delete(); + v2.delete(); + }); + + test("should multiply in-place", () => { + const v = new BCLIBC._Vector(1, 2, 3); + + v.imul(2); + + expect(v.x).toBe(2); + expect(v.y).toBe(4); + expect(v.z).toBe(6); + + v.delete(); + }); + + test("should divide in-place", () => { + const v = new BCLIBC._Vector(10, 20, 30); + + v.idiv(10); + + expect(v.x).toBe(1); + expect(v.y).toBe(2); + expect(v.z).toBe(3); + + v.delete(); + }); + + test("should support method chaining", () => { + const v = new BCLIBC._Vector(1, 0, 0); + const temp = new BCLIBC._Vector(5, 5, 5); + + v.imul(10).iadd(temp); + + expect(v.x).toBe(15); + expect(v.y).toBe(5); + expect(v.z).toBe(5); + + v.delete(); + temp.delete(); + }); + }); + + describe("Vector Properties", () => { + test("should calculate magnitude correctly", () => { + const v = new BCLIBC._Vector(3, 4, 0); + const mag = v.mag(); + + expect(mag).toBe(5); + + v.delete(); + }); + + test("should calculate magnitude squared", () => { + const v = new BCLIBC._Vector(3, 4, 0); + const magSq = v.magSquared(); + + expect(magSq).toBe(25); + + v.delete(); + }); + + test("should normalize vector (create new)", () => { + const v = new BCLIBC._Vector(3, 4, 0); + const normalized = v.norm(); + + expect(normalized.x).toBeCloseTo(0.6, 5); + expect(normalized.y).toBeCloseTo(0.8, 5); + expect(normalized.z).toBeCloseTo(0, 5); + + const mag = normalized.mag(); + expect(mag).toBeCloseTo(1, 5); + + v.delete(); + normalized.delete(); + }); + + test("should normalize in-place", () => { + const v = new BCLIBC._Vector(3, 4, 0); + + v.inorm(); + + expect(v.x).toBeCloseTo(0.6, 5); + expect(v.y).toBeCloseTo(0.8, 5); + expect(v.z).toBeCloseTo(0, 5); + + const mag = v.mag(); + expect(mag).toBeCloseTo(1, 5); + + v.delete(); + }); + + test("unit vector should have magnitude 1", () => { + const vectors = [ + new BCLIBC._Vector(10, 0, 0), + new BCLIBC._Vector(0, 20, 0), + new BCLIBC._Vector(5, 5, 5), + new BCLIBC._Vector(3, 4, 12), + ]; + + vectors.forEach((v) => { + v.inorm(); + expect(v.mag()).toBeCloseTo(1, 5); + v.delete(); + }); + }); + }); + + describe("Fused Operations", () => { + test("fusedMultiplyAdd should compute v += other * scalar", () => { + const v = new BCLIBC._Vector(100, 0, 0); + const gravity = new BCLIBC._Vector(0, -9.8, 0); + + v.fusedMultiplyAdd(gravity, 0.1); + + expect(v.x).toBeCloseTo(100, 5); + expect(v.y).toBeCloseTo(-0.98, 5); + expect(v.z).toBeCloseTo(0, 5); + + v.delete(); + gravity.delete(); + }); + + test("fusedMultiplySubtract should compute v -= other * scalar", () => { + const v = new BCLIBC._Vector(100, 100, 0); + const drag = new BCLIBC._Vector(1, 0, 0); + + v.fusedMultiplySubtract(drag, 10); + + expect(v.x).toBeCloseTo(90, 5); + expect(v.y).toBeCloseTo(100, 5); + expect(v.z).toBeCloseTo(0, 5); + + v.delete(); + drag.delete(); + }); + + test("linearCombination should compute v = a*sA + b*sB", () => { + const v = new BCLIBC._Vector(); + const a = new BCLIBC._Vector(1, 0, 0); + const b = new BCLIBC._Vector(0, 1, 0); + + v.linearCombination(a, 3, b, 4); + + expect(v.x).toBeCloseTo(3, 5); + expect(v.y).toBeCloseTo(4, 5); + expect(v.z).toBeCloseTo(0, 5); + + v.delete(); + a.delete(); + b.delete(); + }); + + test("linearCombination4 should compute 4-vector combination", () => { + const v = new BCLIBC._Vector(); + const a = new BCLIBC._Vector(1, 0, 0); + const b = new BCLIBC._Vector(0, 1, 0); + const c = new BCLIBC._Vector(0, 0, 1); + const d = new BCLIBC._Vector(1, 1, 1); + + v.linearCombination4(a, 1, b, 2, c, 3, d, 4); + + expect(v.x).toBeCloseTo(5, 5); // 1 + 0 + 0 + 4 + expect(v.y).toBeCloseTo(6, 5); // 0 + 2 + 0 + 4 + expect(v.z).toBeCloseTo(7, 5); // 0 + 0 + 3 + 4 + + v.delete(); + a.delete(); + b.delete(); + c.delete(); + d.delete(); + }); + }); + + describe("Physics Simulation", () => { + test("should simulate projectile motion step", () => { + const position = new BCLIBC._Vector(0, 0, 0); + const velocity = new BCLIBC._Vector(100, 100, 0); + const gravity = new BCLIBC._Vector(0, -9.8, 0); + const dt = 0.1; + + // Update velocity: v += g * dt + velocity.fusedMultiplyAdd(gravity, dt); + + // Update position: p += v * dt + position.fusedMultiplyAdd(velocity, dt); + + expect(position.x).toBeCloseTo(10, 5); + expect(position.y).toBeCloseTo(9.902, 3); + expect(position.z).toBeCloseTo(0, 5); + + position.delete(); + velocity.delete(); + gravity.delete(); + }); + + test("should simulate multiple time steps", () => { + const position = new BCLIBC._Vector(0, 10, 0); + const velocity = new BCLIBC._Vector(10, 0, 0); + const gravity = new BCLIBC._Vector(0, -9.8, 0); + const dt = 0.01; + + // Simulate until projectile hits ground + let steps = 0; + while (position.y > 0 && steps < 1000) { + velocity.fusedMultiplyAdd(gravity, dt); + position.fusedMultiplyAdd(velocity, dt); + steps++; + } + + expect(steps).toBeGreaterThan(0); + expect(steps).toBeLessThan(1000); + expect(position.y).toBeLessThanOrEqual(0); + + position.delete(); + velocity.delete(); + gravity.delete(); + }); + }); + + describe("Performance", () => { + const iterations = 10000; + + test("regular add performance", () => { + const v1 = new BCLIBC._Vector(1, 2, 3); + const v2 = new BCLIBC._Vector(4, 5, 6); + + const start = performance.now(); + for (let i = 0; i < iterations; i++) { + const tmp = v1.add(v2); + tmp.delete(); + } + const elapsed = performance.now() - start; + + expect(elapsed).toBeLessThan(5000); + + v1.delete(); + v2.delete(); + }); + + test("in-place iadd should be faster than regular add", () => { + const v1 = new BCLIBC._Vector(1, 2, 3); + const v2 = new BCLIBC._Vector(4, 5, 6); + const v3 = new BCLIBC._Vector(1, 2, 3); + + // Regular add + const start1 = performance.now(); + for (let i = 0; i < iterations; i++) { + const tmp = v1.add(v2); + tmp.delete(); + } + const elapsed1 = performance.now() - start1; + + // In-place add + const start2 = performance.now(); + for (let i = 0; i < iterations; i++) { + v3.iadd(v2); + } + const elapsed2 = performance.now() - start2; + + expect(elapsed2).toBeLessThan(elapsed1); + + v1.delete(); + v2.delete(); + v3.delete(); + }); + }); + + describe("Memory Management", () => { + test("should not leak memory with proper cleanup", () => { + const vectors = []; + + for (let i = 0; i < 100; i++) { + vectors.push(new BCLIBC._Vector(i, i * 2, i * 3)); + } + + vectors.forEach((v) => v.delete()); + + // If we got here without crashing, memory management works + expect(vectors.length).toBe(100); + }); + + test("should handle operations on many vectors", () => { + const count = 1000; + const vectors = []; + + for (let i = 0; i < count; i++) { + const v = new BCLIBC._Vector(i, i, i); + v.inorm(); + vectors.push(v); + } + + // vectors.forEach((v, i) => { + // expect(v.mag()).toBeCloseTo(1, 5); + // v.delete(); + // }); + vectors.forEach((v) => { + const m = v.mag(); + if (m !== 0) { + expect(m).toBeCloseTo(1, 5); + } + }); + + expect(vectors.length).toBe(count); + }); + }); +}); diff --git a/__tests__/zero-finding.test.ts b/__tests__/zero-finding.test.ts index b9fa48a..dc04195 100644 --- a/__tests__/zero-finding.test.ts +++ b/__tests__/zero-finding.test.ts @@ -1,22 +1,21 @@ import { expect } from "@jest/globals"; import { Ammo, - BaseEngineConfig, - Calculator, DragModel, - EulerIntegrationEngine, - RK4IntegrationEngine, - Shot, - Table, + IntegrationMethod, + DragTables, + TrajFlag, UNew, Unit, Weapon, } from "../src"; +import { Shot } from "../src/shot"; +import { Calculator } from "../src/interface"; const createShot = () => { const dm = new DragModel({ bc: 0.759, - dragTable: Table.G1, + dragTable: DragTables.G1, weight: UNew.Gram(108), diameter: UNew.Millimeter(23), length: UNew.Millimeter(108.2), @@ -26,15 +25,15 @@ const createShot = () => { return new Shot({ weapon, ammo }); }; -const DISTANCES_FOR_CHECKING = [7126.05]; +const DISTANCES_FOR_CHECKING = [100, 500, 1000]; -const engines = [ - { engine: EulerIntegrationEngine }, - { engine: RK4IntegrationEngine }, +const methods = [ + { name: "RK4", method: IntegrationMethod.RK4 }, + { name: "EULER", method: IntegrationMethod.EULER }, ]; -const testCases = engines.flatMap((engineObj) => - DISTANCES_FOR_CHECKING.map((distance) => ({ engineObj, distance })), +const testCases = methods.flatMap((obj) => + DISTANCES_FOR_CHECKING.map((distance) => ({ obj, distance })) ); describe("Unit test for zero finding in ballistic calculator", () => { @@ -42,29 +41,28 @@ describe("Unit test for zero finding in ballistic calculator", () => { // The test name now uses Jest's $propertyName syntax for interpolation test.each(testCases)( "test_set_weapon_zero with $engineObj.name and distance $distance", - ({ engineObj, distance }) => { + async ({ obj, distance }) => { // Destructure the properties from the single test case object - const { engine } = engineObj; - const config: Partial = { - cMinimumVelocity: 0, - }; - const zeroMinVelocityCalc = new Calculator({ engine, config }); + const { method } = obj; + const zeroMinVelocityCalc = new Calculator({ + method, + config: { cMinimumVelocity: 0 }, + }); const shot = createShot(); // Create a new shot for each test run - zeroMinVelocityCalc.setWeaponZero(shot, UNew.Meter(distance)); + await zeroMinVelocityCalc.setWeaponZero(shot, UNew.Meter(distance)); console.log( - `${engine} - barrelElevation for ${distance}m: ${shot.barrelElevation.In(Unit.Degree)} degrees`, + `${method} - barrelElevation for ${distance}m: ${shot.barrelElevation.In(Unit.Degree)} degrees` ); - const t = zeroMinVelocityCalc.fire({ + const hit = await zeroMinVelocityCalc.fire({ shot, trajectoryRange: UNew.Meter(distance), - extraData: true, - }).trajectory; + filterFlags: TrajFlag.ALL, + }); + const t = hit.trajectory; const finalHitDistance = t[t.length - 1].distance.In(Unit.Meter); - expect(Math.abs(finalHitDistance - distance)).toBeLessThanOrEqual( - 1.0, - ); - }, + expect(Math.abs(finalHitDistance - distance)).toBeLessThanOrEqual(1.0); + } ); }); diff --git a/doc/units.md b/doc/units.md index 89d186a..31e1abb 100644 --- a/doc/units.md +++ b/doc/units.md @@ -1,66 +1,66 @@ # Units Module Documentation - [AbstractUnit Class](#abstractunit-class) - - [Constructor](#constructor) - - [Methods](#methods) - - [`toString(): string`](#tostring-string) - - [`_unit_support_error(value: number, units: Unit): number`](#_unit_support_error-value-number-units-unit-number) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - - [`to(units: Unit): AbstractUnit`](#to-units-unit-abstractunit) - - [`in(units: Unit): number`](#in-units-unit-number) - - [`get units: Unit`](#get-units-unit) - - [`get rawValue: number`](#get-rawvalue-number) + - [Constructor](#constructor) + - [Methods](#methods) + - [`toString(): string`](#tostring-string) + - [`_unit_support_error(value: number, units: Unit): number`](#_unit_support_error-value-number-units-unit-number) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [`to(units: Unit): AbstractUnit`](#to-units-unit-abstractunit) + - [`in(units: Unit): number`](#in-units-unit-number) + - [`get units: Unit`](#get-units-unit) + - [`get rawValue: number`](#get-rawvalue-number) - [Angular Class (extends AbstractUnit)](#angular-class-extends-abstractunit) - - [Methods](#methods-1) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-1) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Distance Class (extends AbstractUnit)](#distance-class-extends-abstractunit) - - [Methods](#methods-2) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-2) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Velocity Class (extends AbstractUnit)](#velocity-class-extends-abstractunit) - - [Methods](#methods-3) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-3) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Weight Class (extends AbstractUnit)](#weight-class-extends-abstractunit) - - [Methods](#methods-4) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-4) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Pressure Class (extends AbstractUnit)](#pressure-class-extends-abstractunit) - - [Methods](#methods-5) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-5) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Temperature Class (extends AbstractUnit)](#temperature-class-extends-abstractunit) - - [Methods](#methods-6) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-6) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Energy Class (extends AbstractUnit)](#energy-class-extends-abstractunit) - - [Methods](#methods-7) - - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) - - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) + - [Methods](#methods-7) + - [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) + - [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) - [Unit Enum](#unit-enum) - [UnitPropsDict Object](#unitpropsdict-object) - [unitTypeCoerce](#unittypecoerce) - ## [AbstractUnit Class](#abstractunit-class) -An abstract class for unit of measure instance definition. It stores the defined unit and value, and applies conversions to other units. +An abstract class for unit of measure instance definition. It stores the defined +unit and value, and applies conversions to other units. ### Constructor - **Parameters:** - - `value` (number): Numeric value of the unit. - - `units` (Unit): Unit as a Unit enum. + - `value` (number): Numeric value of the unit. + - `units` (Unit): Unit as a Unit enum. ### Methods @@ -71,13 +71,14 @@ Returns a human-readable representation of the value with its unit. #### [`_unit_support_error(value: number, units: Unit): number`](#_unit_support_error-value-number-units-unit-number) Validates the units. + - Parameters: - - `value` (number): Value of the unit. - - `units` (Unit): Unit enum type. + - `value` (number): Value of the unit. + - `units` (Unit): Unit enum type. - Returns: number - Throws: - - `TypeError` when the provided units are not of the expected type. - - `Error` when the provided units are not supported. + - `TypeError` when the provided units are not of the expected type. + - `Error` when the provided units are not supported. #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) @@ -113,11 +114,13 @@ Angular unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Angular-specific conversion logic. +Overrides the method in AbstractUnit to provide Angular-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Angular-specific conversion logic. +Overrides the method in AbstractUnit to provide Angular-specific conversion +logic. --- @@ -129,11 +132,13 @@ Distance unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Distance-specific conversion logic. +Overrides the method in AbstractUnit to provide Distance-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Distance-specific conversion logic. +Overrides the method in AbstractUnit to provide Distance-specific conversion +logic. --- @@ -145,11 +150,13 @@ Velocity unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Velocity-specific conversion logic. +Overrides the method in AbstractUnit to provide Velocity-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Velocity-specific conversion logic. +Overrides the method in AbstractUnit to provide Velocity-specific conversion +logic. --- @@ -161,11 +168,13 @@ Weight unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Weight-specific conversion logic. +Overrides the method in AbstractUnit to provide Weight-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Weight-specific conversion logic. +Overrides the method in AbstractUnit to provide Weight-specific conversion +logic. --- @@ -177,11 +186,13 @@ Pressure unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Pressure-specific conversion logic. +Overrides the method in AbstractUnit to provide Pressure-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Pressure-specific conversion logic. +Overrides the method in AbstractUnit to provide Pressure-specific conversion +logic. --- @@ -193,11 +204,13 @@ Temperature unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Temperature-specific conversion logic. +Overrides the method in AbstractUnit to provide Temperature-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Temperature-specific conversion logic. +Overrides the method in AbstractUnit to provide Temperature-specific conversion +logic. --- @@ -209,11 +222,13 @@ Energy unit class, extending [AbstractUnit](#abstractunit-class). #### [`toRaw(value: number, units: Unit): number`](#toraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Energy-specific conversion logic. +Overrides the method in AbstractUnit to provide Energy-specific conversion +logic. #### [`fromRaw(value: number, units: Unit): number`](#fromraw-value-number-units-unit-number) -Overrides the method in AbstractUnit to provide Energy-specific conversion logic. +Overrides the method in AbstractUnit to provide Energy-specific conversion +logic. --- @@ -231,11 +246,13 @@ A dictionary of properties for the Unit enum type. ## unitTypeCoerce -Coerces the given instance to the specified class type or creates a new instance. +Coerces the given instance to the specified class type or creates a new +instance. + - Parameters: - - `instance` (Object): The instance to coerce or create. - - `expectedClass` (Class): The expected class type. - - `defaultUnit` (Unit): The default unit for creating a new instance. + - `instance` (Object): The instance to coerce or create. + - `expectedClass` (Class): The expected class type. + - `defaultUnit` (Unit): The default unit for creating a new instance. - Returns: `AbstractUnit` or `Object` - Throws: - - `TypeError` if the instance is \ No newline at end of file + - `TypeError` if the instance is diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..a5949c4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,19 @@ +export default { + preset: "ts-jest/presets/js-with-ts-esm", + testEnvironment: "node", + moduleNameMapper: { + // Регулярка має збігатися з твоїм аліасом + "^@wasm/(.*)$": "/build/$1", + "^@src/(.*)$": "/src/$1", + }, + setupFilesAfterEnv: ["/src/_wasm.ts"], + // Ignore tests in emsdk + testPathIgnorePatterns: ["/node_modules/", "/emsdk/"], + // If here is JS, not need transform + transformIgnorePatterns: ["/node_modules/", "/emsdk/"], + transform: { + "^.+\\.ts$": ["ts-jest", { useESM: true }], + "^.+\\.tsx?$": ["ts-jest", { useESM: true }], + "^.+\\.js$": "babel-jest", // <- для ES6 JS (dist/bclibc.js) + }, +}; diff --git a/lib/bclibc b/lib/bclibc new file mode 160000 index 0000000..5669f35 --- /dev/null +++ b/lib/bclibc @@ -0,0 +1 @@ +Subproject commit 5669f35d9752c58fd93f110c4bc6a563cdc8cb51 diff --git a/lib/emsdk b/lib/emsdk new file mode 160000 index 0000000..c9ef2c9 --- /dev/null +++ b/lib/emsdk @@ -0,0 +1 @@ +Subproject commit c9ef2c9d6000341d667a32a807ba47ca489d1e1b diff --git a/package.json b/package.json index 116d92b..9643d01 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,65 @@ { - "type": "module", - "name": "js-ballistics", - "version": "2.2.0-beta.2", - "description": "ISC library for small arms ballistic calculations (JavaScript ES6+)", - "main": "dist/index.js", - "scripts": { - "test": "jest", - "prettier": "prettier ./src ./__tests__ --write", - "build": "tsc", - "prepublishOnly": "npm run build" - }, - "files": [ - "dist/**/*.js", - "dist/**/*.d.ts" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/o-murphy/js-ballistics.git" - }, - "keywords": [ - "ballistic", - "calculator", - "js", - "es6" - ], - "author": "o-murphy", - "license": "ISC", - "bugs": { - "url": "https://github.com/o-murphy/js-ballistics/issues" - }, - "homepage": "https://github.com/o-murphy/js-ballistics#readme", - "devDependencies": { - "@babel/core": "^7.28.6", - "@babel/preset-env": "^7.28.6", - "@babel/preset-typescript": "^7.28.5", - "@jest/globals": "^30.2.0", - "@types/jest": "^30.0.0", - "babel-jest": "^30.2.0", - "jest": "^30.2.0", - "prettier": "^3.7.4", - "ts-jest": "^29.4.6", - "typescript": "^5.9.3" + "name": "js-ballistics", + "type": "module", + "version": "2.2.0-beta.2", + "description": "ISC library for small arms ballistic calculations (JavaScript ES6+)", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" } + }, + "scripts": { + "test": "NODE_OPTIONS='--experimental-vm-modules' jest", + "test:rebuild": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "build": "make build", + "prepublishOnly": "npm run build", + "deploy": "touch dist/.nojekyll && gh-pages -d dist" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/o-murphy/js-ballistics.git" + }, + "keywords": [ + "ballistic", + "calculator", + "js", + "es6" + ], + "author": "o-murphy", + "license": "ISC", + "bugs": { + "url": "https://github.com/o-murphy/js-ballistics/issues" + }, + "homepage": "https://github.com/o-murphy/js-ballistics#readme", + "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "@babel/preset-typescript": "^7.28.5", + "@jest/globals": "^30.3.0", + "@types/emscripten": "^1.41.5", + "@types/jest": "^30.0.0", + "@types/node": "^25.0.3", + "babel-jest": "^30.3.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jest": "^29.5.0", + "gh-pages": "^6.3.0", + "jest": "^30.3.0", + "prettier": "^3.8.1", + "ts-jest": "^29.4.6", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + } } diff --git a/src/_wasm.ts b/src/_wasm.ts new file mode 100644 index 0000000..5dca508 --- /dev/null +++ b/src/_wasm.ts @@ -0,0 +1,106 @@ +/** + * WASM Module Manager + * + * Centralized singleton manager for the BCLIBC WebAssembly module. + * + * Usage: + * ```typescript + * import { WasmManager } from './_wasm'; + * await WasmManager.init(); + * const bclibc = WasmManager.get(); + * ``` + */ + +import MainModuleFactory from "@wasm/bclibc"; +import type { + MainModule as BCLIBC, + _TrajFlag, + _IntegrationMethod, + _TerminationReason, +} from "@wasm/bclibc"; +import * as Exceptions from "./exceptions"; + +// Export all raw types from the WASM module +export type * from "@wasm/bclibc"; + +// Register exception classes in globalThis for C++ Embind access +if (typeof globalThis !== "undefined") { + Object.assign(globalThis, Exceptions); +} + +let bclibcReady: Promise | null = null; +let instance: BCLIBC | null = null; + +/** + * Initializes the WASM module factory + */ +export const loadBclibc = (): Promise => { + if (!bclibcReady) { + bclibcReady = MainModuleFactory().then((module) => { + instance = module; + return module; + }); + } + return bclibcReady; +}; + +/** + * Singleton Manager for BCLIBC WASM + */ +export const WasmManager = { + init: loadBclibc, + get: (): BCLIBC => { + if (!instance) { + throw new Error( + "BCLIBC_WasmManager: WASM module not initialized. Call await WasmManager.init() first." + ); + } + return instance; + }, +}; + +/** + * Ballistic Solver Integration Methods + */ +export const IntegrationMethod = { + RK4: 0 as any, + EULER: 1 as any, +} as const; + +/** + * Trajectory Data Flags + */ +export const TrajFlag = { + NONE: 0 as any, + ZERO_UP: 1 as any, + ZERO_DOWN: 2 as any, + ZERO: 3 as any, // ZERO_UP | ZERO_DOWN + MACH: 4 as any, + RANGE: 8 as any, + APEX: 16 as any, + ALL: 31 as any, + MRT: 32 as any, +} as const; + +/** + * Trajectory Termination Reasons + */ +export const TerminationReason = { + NO_TERMINATE: 0 as any, + TARGET_RANGE_REACHED: 1 as any, + MINIMUM_VELOCITY_REACHED: 2 as any, + MAXIMUM_DROP_REACHED: 3 as any, + MINIMUM_ALTITUDE_REACHED: 4 as any, + HANDLER_REQUESTED_STOP: 5 as any, +} as const; + +// Clean type exports for TypeScript +export type IntegrationMethod = _IntegrationMethod; +export type TrajFlag = _TrajFlag; +export type TerminationReason = _TerminationReason; + +export type Config = import("@wasm/bclibc")._Config; +export type HitOutput = import("@wasm/bclibc")._HitOutput; +export type TrajectoryRequest = import("@wasm/bclibc")._TrajectoryRequest; +export type BaseTrajData = import("@wasm/bclibc")._BaseTrajData; +export type ShotPropsInput = import("@wasm/bclibc")._ShotPropsInput; diff --git a/src/conditions.ts b/src/conditions.ts index 3bf501a..cf8e450 100644 --- a/src/conditions.ts +++ b/src/conditions.ts @@ -1,5 +1,6 @@ // Classes to define zeroing or current environment conditions +import { _Atmosphere, _Coriolis, _Wind } from "./_wasm"; import { cLapseRateImperial, cStandardTemperatureF, @@ -12,13 +13,10 @@ import { cMaxWindDistanceFeet, cLowestTempF, cStandardDensityMetric, - cLapseRateKperFoot, cStandardPressureMetric, cLapseRateMetric, cStandardTemperatureC, - cSpeedOfSoundMetric, } from "./constants"; -import { Ammo, Weapon } from "./munition"; import { unitTypeCoerce, UNew, @@ -29,7 +27,8 @@ import { Angular, preferredUnits, } from "./unit"; -import Vector from "./vector"; + +export { Atmo, Vacuum, Wind, Coriolis }; class Atmo { protected _altitude: Distance; @@ -41,13 +40,11 @@ class Atmo { protected _densityRatio: number; // readonly mach: Velocity - protected _mach: number; - protected _a0: number; - protected _t0: number; - protected _p0: number; - public static cLowestTempC: number = UNew.Fahrenheit(cLowestTempF).In( - Temperature.Celsius, - ); + public _mach: number; + public _a0: number; + public _t0: number; + public _p0: number; + public static cLowestTempC: number = UNew.Fahrenheit(cLowestTempF).In(Temperature.Celsius); protected _initializing: boolean; @@ -55,51 +52,47 @@ class Atmo { * Represents atmospheric conditions and performs density calculations. * * @param {Object} [options] - The options for initializing the atmospheric conditions. - * @param {number | Distance | null} [options.altitude=null] - Altitude above sea level, or a distance object. - * @param {number | Pressure | null} [options.pressure=null] - Atmospheric pressure, or a pressure object. - * @param {number | Temperature | null} [options.temperature=null] - Temperature in Fahrenheit, or a temperature object. + * @param {number | Distance} [options.altitude=undefined] - Altitude above sea level, or a distance object. + * @param {number | Pressure} [options.pressure=undefined] - Atmospheric pressure, or a pressure object. + * @param {number | Temperature} [options.temperature=undefined] - Temperature in Fahrenheit, or a temperature object. * @param {number} [options.humidity=0.0] - Relative humidity as a decimal (default: 0.0, where 1.0 is 100%). - * @param {number | Temperature | null} [options.powderT=null] - Powder temperature (default: null). + * @param {number | Temperature} [options.powderT=undefined] - Powder temperature (default: undefined). */ constructor({ - altitude = null, - pressure = null, - temperature = null, + altitude = undefined, + pressure = undefined, + temperature = undefined, humidity = 0.0, - powderT = null, + powderTemperature = undefined, }: { - altitude?: number | Distance | null; - pressure?: number | Pressure | null; - temperature?: number | Temperature | null; + altitude?: number | Distance; + pressure?: number | Pressure; + temperature?: number | Temperature; humidity?: number; - powderT?: number | Temperature | null; + powderTemperature?: number | Temperature; } = {}) { this._initializing = true; - this._altitude = unitTypeCoerce( - altitude ?? 0, - Distance, - preferredUnits.distance, - ); + this._altitude = unitTypeCoerce(altitude ?? 0, Distance, preferredUnits.distance); this._pressure = unitTypeCoerce( pressure ?? Atmo.standardPressure(this.altitude), Pressure, - preferredUnits.pressure, + preferredUnits.pressure ); this._temperature = unitTypeCoerce( temperature ?? Atmo.standardTemperature(this.altitude), Temperature, - preferredUnits.temperature, + preferredUnits.temperature ); // If powder_temperature not provided we use atmospheric temperature: this._powderTemp = unitTypeCoerce( - powderT ?? this.temperature, + powderTemperature ?? this.temperature, Temperature, - preferredUnits.temperature, + preferredUnits.temperature ); this._t0 = this.temperature.In(Temperature.Celsius); this._p0 = this.pressure.In(Pressure.hPa); - this._a0 = this.altitude.In(Distance.Foot); + this._a0 = this.altitude.foot; this._mach = Atmo.machF(this._temperature.In(Temperature.Fahrenheit)); this.humidity = humidity; this._initializing = false; @@ -149,8 +142,7 @@ class Atmo { updateDensityRatio() { this._densityRatio = - Atmo.calculateAirDensity(this._t0, this._p0, this.humidity) / - cStandardDensityMetric; + Atmo.calculateAirDensity(this._t0, this._p0, this.humidity) / cStandardDensityMetric; } get densityMetric(): number { @@ -161,51 +153,8 @@ class Atmo { return this._densityRatio * cStandardDensity; } - temperatureAtAltitude(altitude: number): number { - let t = (altitude - this._a0) * cLapseRateKperFoot + this._t0; - if (t < Atmo.cLowestTempC) { - t = Atmo.cLowestTempC; - console.warn(`Temperature interpolated from altitude fell below minimum temperature limit. - Model not accurate here. Temperature bounded at cLowestTempF: ${cLowestTempF}°F.`); - } - return t; - } - - pressureAtAltitude(altitude: number): number { - return ( - this._p0 * - Math.pow( - 1 + - (cLapseRateKperFoot * (altitude - this._a0)) / - (this._t0 + cDegreesCtoK), - cPressureExponent, - ) - ); - } - - getDensityFactorAndMachForAltitude(altitude: number): [number, number] { - // Within 30 ft of initial altitude use initial values to save compute - if (Math.abs(this._a0 - altitude) < 30) { - return [this._densityRatio, this._mach]; - } - if (altitude > 36089) { - console.warn( - "Density request for altitude above troposphere. Atmospheric model not valid here.", - ); - } - const t = this.temperatureAtAltitude(altitude) + cDegreesCtoK; - const mach = UNew.MPS(Atmo.machK(t)).In(Velocity.FPS); - const p = this.pressureAtAltitude(altitude); - const densityDelta = ((this._t0 + cDegreesCtoK) * p) / (this._p0 * t); - const densityRatio = this._densityRatio * densityDelta; - return [densityRatio, mach]; - } - static standardTemperature(altitude: Distance): Temperature { - return UNew.Fahrenheit( - cStandardTemperatureF + - altitude.In(Distance.Foot) * cLapseRateImperial, - ); + return UNew.Fahrenheit(cStandardTemperatureF + altitude.foot * cLapseRateImperial); } static standardPressure(altitude: Distance): Pressure { @@ -215,28 +164,24 @@ class Atmo { 1 + (cLapseRateMetric * altitude.In(Distance.Meter)) / (cStandardTemperatureC + cDegreesCtoK), - cPressureExponent, - ), + cPressureExponent + ) ); } static icao({ // Destructure directly in the parameter list altitude = 0, // Default altitude to 0 if not provided in the options object - temperature = null, // No default here; handle null/undefined explicitly in the body + temperature = undefined, // No default here; handle undefined explicitly in the body humidity = cStandardHumidity, // Default humidity to cStandardHumidity if not provided }: { // Type of the destructured options object altitude?: number | Distance; - temperature?: number | Temperature | null; + temperature?: number | Temperature; humidity?: number; } = {}) { - const _altitude = unitTypeCoerce( - altitude, - Distance, - preferredUnits.distance, - ); - if (temperature === null || temperature === undefined) { + const _altitude = unitTypeCoerce(altitude, Distance, preferredUnits.distance); + if (temperature === undefined) { temperature = Atmo.standardTemperature(_altitude); } const pressure = Atmo.standardPressure(_altitude); @@ -247,29 +192,13 @@ class Atmo { static machF(fahrenheit: number): number { if (fahrenheit < -cDegreesFtoR) { - console.warn( - `Invalid temperature: ${fahrenheit}°F. Adjusted to (${cLowestTempF}°F).`, - ); + console.warn(`Invalid temperature: ${fahrenheit}°F. Adjusted to (${cLowestTempF}°F).`); + fahrenheit = cLowestTempF; // Clamp to the minimum temperature } return Math.sqrt(fahrenheit + cDegreesFtoR) * cSpeedOfSoundImperial; } - static machC(celsius: number): number { - if (celsius < -cDegreesCtoK) { - const badTemp = celsius; - celsius = Atmo.cLowestTempC; - console.warn( - `Invalid temperature: ${badTemp}°C. Adjusted to (${celsius}°C).`, - ); - } - return Atmo.machK(celsius + cDegreesCtoK); - } - - static machK(kelvin: number): number { - return Math.sqrt(kelvin) * cSpeedOfSoundMetric; - } - - static calculateAirDensity(t: number, p: number, humidity: number): number { + static calculateAirDensity(t: number, p_hpa: number, humidity: number): number { const R = 8.314472; // J/(mol·K), universal gas constant const M_a = 28.96546e-3; // kg/mol, molar mass of dry air const M_v = 18.01528e-3; // kg/mol, molar mass of water vapor @@ -286,11 +215,7 @@ class Atmo { return alpha + beta * p + gamma * T ** 2; }; - const compressibilityFactor = ( - p: number, - T: number, - x_v: number, - ): number => { + const compressibilityFactor = (p: number, T: number, x_v: number): number => { const a0 = 1.58123e-6; const a1 = -2.9331e-8; const a2 = 1.1043e-10; @@ -301,36 +226,49 @@ class Atmo { const d = 1.83e-11; const e = -0.765e-8; - const t = T - cDegreesCtoK; + const t_l = T - cDegreesCtoK; const Z = 1 - (p / T) * (a0 + - a1 * t + - a2 * t ** 2 + - (b0 + b1 * t) * x_v + - (c0 + c1 * t) * x_v ** 2) + + a1 * t_l + + a2 * t_l ** 2 + + (b0 + b1 * t_l) * x_v + + (c0 + c1 * t_l) * x_v ** 2) + (p / T) ** 2 * (d + e * x_v ** 2); return Z; }; - // Temperature in Kelvin + // Normalize humidity to fraction [0..1] + let rh_frac = humidity > 1.0 ? humidity / 100.0 : humidity; + rh_frac = Math.max(0.0, Math.min(1.0, rh_frac)); + + // Convert inputs for CIPM equations const T_K = t + cDegreesCtoK; + const p = p_hpa * 100.0; // hPa -> Pa // Calculation of saturated vapor pressure and enhancement factor - const p_sv = saturationVaporPressure(T_K); - const f = enchancementFactor(p, t); + const p_sv = saturationVaporPressure(T_K); // Pa (saturated vapor pressure) + const f = enchancementFactor(p, t); // Enhancement factor (p in Pa, t in °C) - // Calculation of partial pressure and mole fraction of water vapor - const p_v = (humidity / 100) * f * p_sv; + // Partial pressure of water vapor and mole fraction + const p_v = rh_frac * f * p_sv; const x_v = p_v / p; // Calculation of compressibility factor const Z = compressibilityFactor(p, T_K, x_v); + return ((p * M_a) / (Z * R * T_K)) * (1 - x_v * (1 - M_v / M_a)); + } - const density = - ((p * M_a) / (Z * R * T_K)) * (1 - x_v * (1 - M_v / M_a)); - return 100 * density; + toWasmAtmo(): _Atmosphere { + return { + t0: this._t0, + a0: this._a0, + p0: this._p0, + mach: this._mach, + density_ratio: this.densityRatio, + cLowestTempC: Atmo.cLowestTempC, + }; } } @@ -341,17 +279,17 @@ class Vacuum extends Atmo { * Represents atmospheric conditions and performs density calculations. * * @param {Object} [options] - The options for initializing the atmospheric conditions. - * @param {number | Distance | null} [options.altitude=null] - Altitude above sea level, or a distance object. - * @param {number | Pressure | null} [options.pressure=null] - Atmospheric pressure, or a pressure object. - * @param {number | Temperature | null} [options.temperature=null] - Temperature in Fahrenheit, or a temperature object. + * @param {number | Distance} [options.altitude=undefined] - Altitude above sea level, or a distance object. + * @param {number | Pressure} [options.pressure=undefined] - Atmospheric pressure, or a pressure object. + * @param {number | Temperature} [options.temperature=undefined] - Temperature in Fahrenheit, or a temperature object. * @param {number} [options.humidity=0.0] - Relative humidity as a decimal (default: 0.0, where 1.0 is 100%). */ constructor({ - altitude = null, - temperature = null, + altitude = undefined, + temperature = undefined, }: { - altitude?: number | Distance | null; - temperature?: number | Temperature | null; + altitude?: number | Distance; + temperature?: number | Temperature; } = {}) { super({ altitude, pressure: 0, temperature, humidity: 0 }); this._pressure = unitTypeCoerce(0, Pressure, preferredUnits.pressure); @@ -371,160 +309,210 @@ class Wind { * Stores wind data at the desired distance. * * @param {Object} [options] - The options for initializing wind data. - * @param {number | Velocity | null} [options.velocity=null] - Wind velocity. Can be a number, a `Velocity` object, or `null`. - * @param {number | Angular | null} [options.directionFrom=null] - Wind direction in relation to the shooter. Can be a number, an `Angular` object, or `null`. - * @param {number | Distance | null} [options.untilDistance=null] - Distance up to which the wind data is applicable. Can be a number, a `Distance` object, or `null`. - * @param {number} [options.maxDistanceFeet=1e8] - Maximum distance in feet up to which the wind data is applicable. Defaults to `1e8`. + * @param {number | Velocity} [options.velocity=undefined] - Wind velocity. Can be a number, a `Velocity` object, or `undefined`. + * @param {number | Angular} [options.directionFrom=undefined] - Wind direction in relation to the shooter. Can be a number, an `Angular` object, or `undefined`. + * @param {number | Distance} [options.untilDistance=undefined] - Distance up to which the wind data is applicable. Can be a number, a `Distance` object, or `undefined`. + * @param {number} [options.maxDistanceFeet=cMaxWindDistanceFeet] - Maximum distance in feet up to which the wind data is applicable. Defaults to `1e8`. */ constructor({ - velocity = null, - directionFrom = null, - untilDistance = null, + velocity = undefined, + directionFrom = undefined, + untilDistance = undefined, maxDistanceFeet = cMaxWindDistanceFeet, }: { - velocity?: number | Velocity | null; - directionFrom?: number | Angular | null; - untilDistance?: number | Distance | null; - maxDistanceFeet?: number | null; + velocity?: number | Velocity; + directionFrom?: number | Angular; + untilDistance?: number | Distance; + maxDistanceFeet?: number; } = {}) { // Coerce input values to appropriate units Wind.MAX_DISTANCE_FEET = maxDistanceFeet ?? cMaxWindDistanceFeet; - this.velocity = unitTypeCoerce( - velocity ?? 0, - Velocity, - preferredUnits.velocity, - ); - this.directionFrom = unitTypeCoerce( - directionFrom ?? 0, - Angular, - preferredUnits.angular, - ); + this.velocity = unitTypeCoerce(velocity ?? 0, Velocity, preferredUnits.velocity); + this.directionFrom = unitTypeCoerce(directionFrom ?? 0, Angular, preferredUnits.angular); this.untilDistance = unitTypeCoerce( untilDistance ?? UNew.Foot(Wind.MAX_DISTANCE_FEET), Distance, - preferredUnits.distance, + preferredUnits.distance ); } - get vector(): Vector { - const windVelocityFps = this.velocity.In(Velocity.FPS); - const windDirectionRad = this.directionFrom.In(Angular.Radian); - const rangeComponent = windVelocityFps * Math.cos(windDirectionRad); - const crossComponent = windVelocityFps * Math.sin(windDirectionRad); - return new Vector(rangeComponent, 0, crossComponent); + toWasmWind(): _Wind { + return { + velocity_fps: this.velocity.fps, + direction_from_rad: this.directionFrom.rad, + until_distance_ft: this.untilDistance.foot, + MAX_DISTANCE_FEET: Wind.MAX_DISTANCE_FEET, + }; } } /** - * Represents the parameters required for calculating a shot's trajectory. + * Precomputed Coriolis helpers for applying Earth's rotation. + * + * The calculator keeps ballistic state in a local range/up/cross (x, y, z) frame where: + * - x axis points down-range + * - y points up + * - z points to the shooter's right + * + * Coriolis forces originate in the Earth-fixed East-North-Up (ENU) frame. + * This class precomputes the scalars to transform between the two frames. + * + * If latitude is provided but not azimuth, falls back on a *flat-fire* approximation: + * - North of equator: deflection is to the right + * - South of equator: deflection is to the left + * + * Full 3D Coriolis acceleration (given azimuth A and latitude L): + * ``` + * 2Ω * [ + * -Vy * cos(L) * sin(A) - Vz * sin(L), + * Vx * cos(L) * sin(A) + Vz * cos(L) * cos(A), + * Vx * sin(L) - Vy * cos(L) * cos(A) + * ] + * ``` + * + * @example + * ```typescript + * // Full 3D Coriolis with latitude and azimuth + * const coriolis = new Coriolis({ + * latitudeDeg: 45, + * azimuthDeg: 90, + * muzzleVelocityFps: 2800 + * }); + * + * // Flat-fire approximation (latitude only) + * const coriolisFlat = new Coriolis({ + * latitudeDeg: 45, + * muzzleVelocityFps: 2800 + * }); + * + * // No Coriolis (returns zero-filled instance) + * const noCoriolis = new Coriolis({ + * muzzleVelocityFps: 2800 + * }); + * ``` */ -class Shot { - lookAngle: Angular; - relativeAngle: Angular; - cantAngle: Angular; +class Coriolis { + /** Sine of the firing latitude, used to project Earth's rotation vector */ + sin_lat: number; + + /** Cosine of the firing latitude */ + cos_lat: number; + + /** Sine of the firing azimuth (undefined in flat-fire mode) */ + sin_az: number; + + /** Cosine of the firing azimuth (undefined in flat-fire mode) */ + cos_az: number; + + /** Projection of local range axis onto geographic east (undefined in flat-fire mode) */ + range_east: number; + + /** Projection of local range axis onto geographic north (undefined in flat-fire mode) */ + range_north: number; + + /** Projection of local cross axis onto geographic east (undefined in flat-fire mode) */ + cross_east: number; - weapon: Weapon; - ammo: Ammo; - atmo: Atmo; - protected _winds: Wind[]; + /** Projection of local cross axis onto geographic north (undefined in flat-fire mode) */ + cross_north: number; + + /** True when no azimuth provided and only 2D flat-fire approximation should run */ + flat_fire_only: boolean; + + /** Muzzle velocity in feet per second (needed by flat-fire approximation) */ + muzzle_velocity_fps: number; /** - * Creates an instance of the Shot class. + * Creates a Coriolis instance for applying Earth's rotation effects. * - * @param {Object} options - The parameters for initializing the shot data. - * @param {Weapon} options.weapon - The weapon used for the shot. - * @param {Ammo} options.ammo - The ammunition used for the shot. - * @param {number | Angular | null} [options.lookAngle=null] - The angle of the shot relative to the horizontal plane. Can be a number, an `Angular` object, or `null`. - * @param {number | Angular | null} [options.relativeAngle=null] - The angle between the shot's trajectory and the intended target line. Can be a number, an `Angular` object, or `null`. - * @param {number | Angular | null} [options.cantAngle=null] - The angle of cant or tilt of the weapon. Can be a number, an `Angular` object, or `null`. - * @param {Atmo | null} [options.atmo=null] - The atmospheric conditions affecting the shot. Can be an `Atmo` object or `null`. - * @param {Wind[] | null} [options.winds=null] - List of wind conditions affecting the shot. Can be an array of `Wind` objects or `null`. + * @param options - Configuration options + * @param options.latitudeDeg - Latitude of shooting location in degrees [-90, 90] + * @param options.azimuthDeg - Azimuth of shooting direction in degrees [0, 360). + * Geographic bearing: 0 = North, 90 = East, 180 = South, 270 = West + * @param options.muzzleVelocityFps - Muzzle velocity in feet per second + * + * @remarks + * - If `latitudeDeg` is undefined, creates zero-filled instance (no Coriolis effects) + * - If `azimuthDeg` is undefined, uses flat-fire approximation (horizontal drift only) + * - If both provided, computes full 3D Coriolis effects */ constructor({ - weapon, - ammo, - lookAngle = null, - relativeAngle = null, - cantAngle = null, - atmo = null, - winds = null, + latitudeDeg, + azimuthDeg, + muzzleVelocityFps, }: { - weapon: Weapon; - ammo: Ammo; - lookAngle?: number | Angular | null; - relativeAngle?: number | Angular | null; - cantAngle?: number | Angular | null; - atmo?: Atmo | null; - winds?: Wind[] | null; + latitudeDeg?: number; + azimuthDeg?: number; + muzzleVelocityFps: number; }) { - this.lookAngle = unitTypeCoerce( - lookAngle ?? 0, - Angular, - preferredUnits.angular, - ); - this.relativeAngle = unitTypeCoerce( - relativeAngle ?? 0, - Angular, - preferredUnits.angular, - ); - this.cantAngle = unitTypeCoerce( - cantAngle ?? 0, - Angular, - preferredUnits.angular, - ); - this.weapon = weapon; - this.ammo = ammo; - this.atmo = atmo ?? Atmo.icao({}); - this.winds = winds; - } - - set winds(winds: Wind[] | null) { - // Changed type to allow null - this._winds = - winds === null || winds.length === 0 ? [new Wind({})] : winds; - } + // No Coriolis - fill with zeros + if (latitudeDeg === undefined) { + this.sin_lat = 0.0; + this.cos_lat = 0.0; + this.sin_az = 0.0; + this.cos_az = 0.0; + this.range_east = 0.0; + this.range_north = 0.0; + this.cross_east = 0.0; + this.cross_north = 0.0; + this.flat_fire_only = false; + this.muzzle_velocity_fps = muzzleVelocityFps; + return; + } - get winds(): Wind[] { - return (this._winds ?? [new Wind({})]) - .slice() // Create a copy of the array - .sort( - (a, b) => a.untilDistance.rawValue - b.untilDistance.rawValue, - ); - } + const lat_rad = (latitudeDeg * Math.PI) / 180; + const sin_lat = Math.sin(lat_rad); + const cos_lat = Math.cos(lat_rad); + + // Flat-fire approximation (no azimuth) + if (azimuthDeg === undefined) { + this.sin_lat = sin_lat; + this.cos_lat = cos_lat; + this.muzzle_velocity_fps = muzzleVelocityFps; + this.sin_az = 0.0; + this.cos_az = 0.0; + this.range_east = 0.0; + this.range_north = 0.0; + this.cross_east = 0.0; + this.cross_north = 0.0; + this.flat_fire_only = true; + return; + } - /** - * Gets the barrel elevation in the vertical plane from the horizontal. - * - * The elevation is calculated by adding the look angle to the vertical component of - * the barrel's elevation based on the cant angle and relative angle. The result is - * converted to radians. - * - * @returns {Angular} The barrel elevation in radians. - */ - get barrelElevation(): Angular { - return UNew.Radian( - this.lookAngle.In(Angular.Radian) + - Math.cos(this.cantAngle.In(Angular.Radian)) * - (this.weapon.zeroElevation.In(Angular.Radian) + - this.relativeAngle.In(Angular.Radian)), - ); + // Full 3D Coriolis + const azimuth_rad = (azimuthDeg * Math.PI) / 180; + const azimuth_sin = Math.sin(azimuth_rad); + const azimuth_cos = Math.cos(azimuth_rad); + + this.sin_lat = sin_lat; + this.cos_lat = cos_lat; + this.muzzle_velocity_fps = muzzleVelocityFps; + this.sin_az = azimuth_sin; + this.cos_az = azimuth_cos; + this.range_east = azimuth_sin; + this.range_north = azimuth_cos; + this.cross_east = azimuth_cos; + this.cross_north = -azimuth_sin; + this.flat_fire_only = false; } /** - * Gets the horizontal angle of the barrel relative to the sight line. - * - * The azimuth angle is calculated based on the cant angle and the relative angle of the - * weapon. The result is converted to radians. + * Converts to WASM-compatible Coriolis format. * - * @returns {Angular} The barrel azimuth in radians. + * @returns Object with all Coriolis parameters, using 0.0 for undefined values */ - get barrelAzimuth(): Angular { - return UNew.Radian( - Math.sin(this.cantAngle.In(Angular.Radian)) * - (this.weapon.zeroElevation.In(Angular.Radian) + - this.relativeAngle.In(Angular.Radian)), - ); + toWasmCoriolis(): _Coriolis { + return { + sin_lat: this.sin_lat, + cos_lat: this.cos_lat, + sin_az: this.sin_az ?? 0.0, + cos_az: this.cos_az ?? 0.0, + range_east: this.range_east ?? 0.0, + range_north: this.range_north ?? 0.0, + cross_east: this.cross_east ?? 0.0, + cross_north: this.cross_north ?? 0.0, + flat_fire_only: this.flat_fire_only, + muzzle_velocity_fps: this.muzzle_velocity_fps, + }; } } - -export { Atmo, Vacuum, Wind, Shot }; diff --git a/src/constants.ts b/src/constants.ts index 153c1a0..06c5d9f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,45 +1,107 @@ -// Global Constants - -export const cStandardHumidity: number = 0.0; // Relative Humidity -export const cPressureExponent: number = 5.255876; // =g*M/R*L - -// ISA, metric preferred units: (https://www.engineeringtoolbox.com/international-standard-atmosphere-d_985.html) -export const cDegreesCtoK: number = 273.15; // °K = °C + 273.15 -export const cStandardTemperatureC: number = 15.0; // °C -export const cLapseRateKperFoot: number = -0.0019812; // Lapse Rate, °K/ft -export const cLapseRateMetric: number = -6.5e-3; // Lapse Rate, °C/m -export const cStandardPressureMetric: number = 1013.25; // hPa -export const cSpeedOfSoundMetric: number = 20.0467; // Mach1 in m/s = cSpeedOfSound * sqrt(°K) -export const cStandardDensityMetric: number = 1.225; // kg/m^3 -export const cDensityImperialToMetric: number = 16.0185; // lb/ft^3 to kg/m^3 - -// ICAO standard atmosphere: -export const cDegreesFtoR: number = 459.67; // °R = °F + 459.67 -export const cStandardTemperatureF: number = 59.0; // °F -export const cLapseRateImperial: number = -3.56616e-3; // Lapse rate, °F/ft -export const cStandardPressure: number = 29.92; // InHg -export const cSpeedOfSoundImperial: number = 49.0223; // Mach1 in fps = cSpeedOfSound * sqrt(°R) -export const cStandardDensity: number = 0.076474; // lb/ft^3 - -// Runtime constants -export const cLowestTempF: number = -130; // °F -export const cMaxWindDistanceFeet: number = 1e8; // From python - -// CIPM 2007 Air Density Constants (from calculate_air_density in conditions.py) -// These constants are internal to the calculate_air_density function in Python, -// but for TypeScript, we'll export them as global constants to maintain consistency -// and enable easier refactoring if needed. -export const CIPM_A0: number = 1.2378847e-5; -export const CIPM_A1: number = -1.9121316e-2; -export const CIPM_A2: number = 33.93711047; -export const CIPM_A3: number = -6.3431645e3; - -export const CIPM_a0: number = 1.58123e-6; -export const CIPM_a1: number = -2.9331e-8; -export const CIPM_a2: number = 1.1043e-10; -export const CIPM_b0: number = 5.707e-6; -export const CIPM_b1: number = -2.051e-8; -export const CIPM_c0: number = 1.9898e-4; -export const CIPM_c1: number = -2.376e-6; -export const CIPM_d: number = 1.83e-11; -export const CIPM_e: number = -0.765e-8; +/** + * Global physical and atmospheric constants for ballistic calculations. + * + * This module defines scientific constants used throughout the ballistic calculations, + * including atmospheric model constants, physical constants, and runtime limits. + * All constants follow international standards (ISA, ICAO) where applicable. + * + * Constant Categories: + * - Global atmosphere constants: Standard conditions and coefficients + * - ISA metric constants: International Standard Atmosphere in metric units + * - ICAO constants: International Civil Aviation Organization standards + * - Conversion factors: Unit conversion constants + * - Runtime limits: Computational bounds and validation limits + * + * References: + * - ISA: https://www.engineeringtoolbox.com/international-standard-atmosphere-d_985.html + * - ICAO: International Civil Aviation Organization standards + * - Physical constants: NIST and other authoritative sources + */ + +/** Standard gravity (g) in ft/s² */ +export const cGravityImperial = 32.17405 as const; + +/** Earth's rotational speed (Ω) in radians per second (rad/s) */ +export const cEarthAngularVelocityRadS = 7.2921159e-5 as const; + +// ============================================================================= +// Global Atmosphere Constants +// ============================================================================= + +/** Standard relative humidity used in atmospheric calculations (%) */ +export const cStandardHumidity = 0.0 as const; + +/** Pressure exponent constant for barometric formula (dimensionless) */ +export const cPressureExponent = 5.255876 as const; // =g*M/R*L + +// Atmospheric model coefficients (used in air density calculations) +export const cA0 = 1.24871 as const; +export const cA1 = 0.0988438 as const; +export const cA2 = 0.00152907 as const; +export const cA3 = -3.07031e-6 as const; +export const cA4 = 4.21329e-7 as const; +export const cA5 = 3.342e-4 as const; + +// ============================================================================= +// ISA Metric Constants (International Standard Atmosphere) +// ============================================================================= + +/** Standard temperature at sea level in Celsius (°C) */ +export const cStandardTemperatureC = 15.0 as const; + +/** Temperature lapse rate in Kelvin per foot (K/ft) */ +export const cLapseRateKperFoot = -0.0019812 as const; + +/** Temperature lapse rate in metric units (°C/m) */ +export const cLapseRateMetric = -6.5e-3 as const; + +/** Standard atmospheric pressure at sea level (hPa) */ +export const cStandardPressureMetric = 1013.25 as const; + +/** Speed of sound coefficient in metric units (m/s per √K) */ +export const cSpeedOfSoundMetric = 20.0467 as const; // Mach1 in m/s = cSpeedOfSound * sqrt(K) + +/** Standard air density at sea level in metric units (kg/m³) */ +export const cStandardDensityMetric = 1.225 as const; + +// ============================================================================= +// ICAO Standard Atmosphere Constants +// ============================================================================= + +/** Standard temperature at sea level in Fahrenheit (°F) */ +export const cStandardTemperatureF = 59.0 as const; + +/** Temperature lapse rate in imperial units (°F/ft) */ +export const cLapseRateImperial = -3.56616e-3 as const; + +/** Standard atmospheric pressure at sea level (InHg) */ +export const cStandardPressure = 29.92 as const; + +/** Speed of sound coefficient in imperial units (fps per √°R) */ +export const cSpeedOfSoundImperial = 49.0223 as const; // Mach1 in fps = cSpeedOfSound * sqrt(°R) + +/** Standard air density at sea level in imperial units (lb/ft³) */ +export const cStandardDensity = 0.076474 as const; + +// ============================================================================= +// Conversion Factors +// ============================================================================= + +/** Celsius to Kelvin conversion constant (K) */ +export const cDegreesCtoK = 273.15 as const; // K = °C + 273.15 + +/** Fahrenheit to Rankine conversion constant (°R) */ +export const cDegreesFtoR = 459.67 as const; // °R = °F + 459.67 + +/** Density conversion factor from imperial to metric units (kg/m³ per lb/ft³) */ +export const cDensityImperialToMetric = 16.0185 as const; + +// ============================================================================= +// Runtime Limits and Validation Constants +// ============================================================================= + +/** Minimum allowed temperature for atmospheric calculations (°F) */ +export const cLowestTempF = -130 as const; + +/** Maximum wind effect distance for computational limits (ft) */ +export const cMaxWindDistanceFeet = 1e8 as const; diff --git a/src/drag_model.ts b/src/drag_model.ts index 2c38ae6..5324a58 100644 --- a/src/drag_model.ts +++ b/src/drag_model.ts @@ -1,53 +1,10 @@ // Import necessary modules and classes -import { - Distance, - unitTypeCoerce, - Weight, - Velocity, - preferredUnits, -} from "./unit"; +import { Distance, unitTypeCoerce, Weight, Velocity, preferredUnits } from "./unit"; // @ts-ignore -import Table from "./drag_tables.js"; -import { - cDegreesCtoK, - cSpeedOfSoundMetric, - cStandardTemperatureC, -} from "./constants"; +import { DragTable, DragTableDataType, makeDataPoints } from "./drag_tables"; +import { cDegreesCtoK, cSpeedOfSoundMetric, cStandardTemperatureC } from "./constants"; -export { - Table, - DragModel, - DragTable, - DragTableDataType, - DragDataPoint, - BCPoint, - DragModelMultiBC, -}; - -/** - * Represents a data point for drag calculation. - */ -class DragDataPoint { - /** - * @param {number} Mach - Mach number at the data point. - * @param {number} CD - Drag coefficient at the data point. - */ - constructor( - public Mach: number, - public CD: number, - ) {} -} - -/** - * Type alias for drag table data. - * Can be an array of objects with Mach and CD properties or DragDataPoint instances. - */ -type DragTableDataType = Array<{ Mach: number; CD: number } | DragDataPoint>; - -/** - * Type alias for an array of DragDataPoint instances. - */ -type DragTable = DragDataPoint[]; +export { DragModel, BCPoint, DragModelMultiBC }; /** * Represents a ballistic coefficient point. @@ -80,9 +37,7 @@ class BCPoint { if (Mach !== null && V !== null) { // More explicit check for null - throw new Error( - "You cannot specify both 'Mach' and 'V' at the same time", - ); + throw new Error("You cannot specify both 'Mach' and 'V' at the same time"); } if (Mach === null && V === null) { @@ -102,17 +57,12 @@ class BCPoint { // This branch should theoretically not be reached due to the earlier check, // but if it somehow is, ensure a clear error or defined state. // For safety, re-throwing the error here emphasizes the contract. - throw new Error( - "Internal error: Mach or V should have been specified but were not.", - ); + throw new Error("Internal error: Mach or V should have been specified but were not."); } } static _machC(): number { - return ( - Math.sqrt(cStandardTemperatureC + cDegreesCtoK) * - cSpeedOfSoundMetric - ); + return Math.sqrt(cStandardTemperatureC + cDegreesCtoK) * cSpeedOfSoundMetric; } } @@ -169,21 +119,9 @@ class DragModel { this.dragTable = makeDataPoints(dragTable); this.bc = bc; - this.weight = unitTypeCoerce( - weight ?? 0, - Weight, - preferredUnits.weight, - ); - this.diameter = unitTypeCoerce( - diameter ?? 0, - Distance, - preferredUnits.diameter, - ); - this.length = unitTypeCoerce( - length ?? 0, - Distance, - preferredUnits.length, - ); + this.weight = unitTypeCoerce(weight ?? 0, Weight, preferredUnits.weight); + this.diameter = unitTypeCoerce(diameter ?? 0, Distance, preferredUnits.diameter); + this.length = unitTypeCoerce(length ?? 0, Distance, preferredUnits.length); // Calculate and set the sectional density and form factor if (weight && diameter) { // FIXME: Check if both > 0 @@ -210,34 +148,13 @@ class DragModel { */ _getSectionalDensity(): number { // Get weight in grains and diameter in inches - const w = this.weight.In(Weight.Grain); - const d = this.diameter.In(Distance.Inch); + const w = this.weight.grain; + const d = this.diameter.inch; // Call the sectionalDensity function to calculate and return the result return sectionalDensity(w, d); } } -/** - * Converts a drag table into an array of `DragDataPoint` objects. - * @param {DragTableDataType} dragTable - The input drag table data, which can be a mix of `DragDataPoint` instances and objects with `Mach` and `CD` properties. - * @returns {DragDataPoint[]} - An array of `DragDataPoint` objects. - * @throws {TypeError} - If any item in the drag table is not a `DragDataPoint` or an object with `Mach` and `CD` properties. - */ -const makeDataPoints = (dragTable: DragTableDataType): DragDataPoint[] => { - return dragTable.map((point) => { - if (point instanceof DragDataPoint) { - return point; // If already a DragDataPoint, return it - } else if ("Mach" in point && "CD" in point) { - // If it's a dictionary with 'Mach' and 'CD', create a new DragDataPoint - return new DragDataPoint(point.Mach, point.CD); - } else { - throw new TypeError( - "All items in dragTable must be of type DragDataPoint or an object with 'Mach' and 'CD' keys.", - ); - } - }); -}; - /** * Calculates and returns the sectional density. * @param {number} weight - The weight value (in grains). @@ -277,13 +194,10 @@ const DragModelMultiBC = ({ const _diameter = unitTypeCoerce( diameter, // Use default parameter, no need for ?? 0 Distance, - preferredUnits.diameter, + preferredUnits.diameter ); if (_weight.rawValue > 0 && _diameter.rawValue > 0) { - bc = sectionalDensity( - _weight.In(Weight.Grain), - _diameter.In(Distance.Inch), - ); + bc = sectionalDensity(_weight.grain, _diameter.inch); } else { bc = 1.0; } @@ -293,7 +207,7 @@ const DragModelMultiBC = ({ const bcInterp = linearInterpolation( _dragTable.map((point) => point.Mach), bcPoints.map((point) => point.Mach), - bcPoints.map((point) => point.BC / bc), + bcPoints.map((point) => point.BC / bc) ); _dragTable.forEach((item, index) => { @@ -301,7 +215,7 @@ const DragModelMultiBC = ({ // to prevent division by zero or NaN propagation if interpolation fails. if (bcInterp[index] === 0 || isNaN(bcInterp[index])) { console.warn( - `Warning: Interpolated BC factor at index ${index} is zero or NaN. CD calculation may result in Infinity/NaN.`, + `Warning: Interpolated BC factor at index ${index} is zero or NaN. CD calculation may result in Infinity/NaN.` ); // You might want to handle this more robustly, e.g., throw an error or use a default value. } @@ -325,11 +239,7 @@ const DragModelMultiBC = ({ * @returns {number[]} - An array of interpolated y-values corresponding to the x-values. * @throws {Error} - Throws an error if the lengths of `xp` and `yp` do not match, or if `x` is empty. */ -const linearInterpolation = ( - x: number[], - xp: number[], - yp: number[], -): number[] => { +const linearInterpolation = (x: number[], xp: number[], yp: number[]): number[] => { if (xp.length !== yp.length) { throw new Error("xp and yp lists must have the same length"); } @@ -337,7 +247,7 @@ const linearInterpolation = ( // Add explicit check for empty xp/yp if (x.length > 0) { throw new Error( - "Cannot interpolate with empty reference points (xp, yp) when x is not empty.", + "Cannot interpolate with empty reference points (xp, yp) when x is not empty." ); } return []; // If all inputs are empty, return empty diff --git a/src/drag_tables.js b/src/drag_tables.ts similarity index 95% rename from src/drag_tables.js rename to src/drag_tables.ts index 8939064..26f47bd 100644 --- a/src/drag_tables.js +++ b/src/drag_tables.ts @@ -1,4 +1,21 @@ -const Table = { +/** + * Represents a data point for drag calculation. + */ +export interface DragDataPoint { + /** + * @param {number} Mach - Mach number at the data point. + * @param {number} CD - Drag coefficient at the data point. + */ + Mach: number; + CD: number; +} + +/** + * Type alias for an array of DragDataPoint instances. + */ +export type DragTable = DragDataPoint[]; + +export const DragTables: Record = { G1: [ { Mach: 0.0, @@ -2589,4 +2606,48 @@ const Table = { ], }; -export default Table; +/** + * Input type that accepts drag data point objects or a table name string. + */ +export type DragTableDataType = DragTable | keyof typeof DragTables; + +/** + * Converts a drag table into an array of `DragDataPoint` objects. + * + * @param dragTable - The input drag table data: either an array with `Mach` and `CD` properties, or a string name of a standard table (e.g., "G1", "G7") + * @returns Array of `DragDataPoint` objects + * @throws {TypeError} If any item in the drag table doesn't have `Mach` and `CD` properties + * @throws {Error} If the table name string is not found in `Table` + * + * @example + * ```typescript + * // Using standard table name + * const g1Table = makeDataPoints("G1"); + * + * // Using custom data points + * const customTable = makeDataPoints([ + * { Mach: 0.0, CD: 0.2629 }, + * { Mach: 0.5, CD: 0.2630 } + * ]); + * ``` + */ +export const makeDataPoints = (dragTable: DragTableDataType): DragDataPoint[] => { + // If string provided, get table from Table object + if (typeof dragTable === "string") { + const tableName = dragTable.toUpperCase(); + if (!(tableName in DragTables)) { + throw new Error( + `Drag table "${dragTable}" not found. Available tables: ${Object.keys(DragTables).join(", ")}` + ); + } + return DragTables[tableName]; + } + + // If array provided, validate and return + return dragTable.map((point) => { + if (typeof point === "object" && point !== null && "Mach" in point && "CD" in point) { + return { Mach: point.Mach, CD: point.CD }; + } + throw new TypeError("All items in dragTable must be objects with 'Mach' and 'CD' keys."); + }); +}; diff --git a/src/engines/base_engine.ts b/src/engines/base_engine.ts deleted file mode 100644 index 4668167..0000000 --- a/src/engines/base_engine.ts +++ /dev/null @@ -1,707 +0,0 @@ -// Conditions module -import { Atmo, Shot, Wind } from "../conditions"; -// TrajectoryData module -import { TrajectoryData, TrajFlag } from "../trajectory_data"; -// Unit module -import { - Angular, - Distance, - UNew, - Weight, - Pressure, - Velocity, - Temperature, -} from "../unit"; -// Vector module -import Vector from "../vector"; -import type { DragTable } from "../drag_model"; -import { TrajectoryRangeError, ZeroFindingError } from "../exceptions"; -import { EngineInterface, GenericConfig } from "../generics/engine"; - -// Export the classes and constants -export { - createTrajectoryRow, - BaseEngineConfig, - BaseTrajectoryData, - BaseEngineTrajectoryProps, - BaseIntegrationEngine, - defaultEngineConfig, - calculateEnergy, - calculateOGW, - getCorrection, - _WindSock, - _TrajectoryDataFilter, - Curve, - CurvePoint, -}; - -// Constants -const cZeroFindingAccuracy: number = 0.000005; -const cMinimumVelocity: number = 50.0; -const cMaximumDrop: number = -15000; -const cMaxIterations: number = 60; -const cGravityConstant: number = -32.17405; -const cMinimumAltitude: number = -1410.748; // ft -const cMaxCalcStepSizeFeet: number = 0.5; // ft - -/** - * Represents a point in a curve with three coefficients. - */ -type CurvePoint = { - readonly a: number; - readonly b: number; - readonly c: number; -}; - -/** - * Represents an array of `CurvePoint` instances. - */ -type Curve = CurvePoint[]; - -/** - * Defines the properties required for a trajectory calculation. - */ -type BaseEngineTrajectoryProps = { - lookAngle: number; - twist: number; - length: number; - diameter: number; - weight: number; - barrelElevation: number; - barrelAzimuth: number; - sightHeight: number; - cantCosine: number; - cantSine: number; - alt0: number; - calcStep: number; - muzzleVelocity: number; - stabilityCoefficient: number; -}; - -interface BaseEngineConfig extends GenericConfig { - cMaxCalcStepSizeFeet: number; - cZeroFindingAccuracy: number; - cMinimumVelocity: number; - cMaximumDrop: number; - cMaxIterations: number; - cGravityConstant: number; - cMinimumAltitude: number; -} - -type BaseTrajectoryData = { - readonly time: number; - readonly position: Vector; - readonly velocity: Vector; - readonly mach: number; -}; - -class _TrajectoryDataFilter { - filter: TrajFlag; - currentFlag: TrajFlag; - seenZero: TrajFlag; - timeOfLastRecord: number; - timeStep: number; - rangeStep: number; - previousMach: number; - previousTime: number; - previousPosition: Vector; - previousVelocity: Vector; - previousVMach: number; - nextRecordDistance: number; - lookAngle: number; - - constructor( - filterFlags: TrajFlag, - rangeStep: number, - initialPosition: Vector, - initialVelocity: Vector, - timeStep: number = 0, - ) { - this.filter = filterFlags; - this.currentFlag = TrajFlag.NONE; - this.seenZero = TrajFlag.NONE; - this.timeStep = timeStep; - this.rangeStep = rangeStep; - this.timeOfLastRecord = 0.0; - this.nextRecordDistance = 0.0; - this.previousMach = 0.0; - this.previousTime = 0.0; - this.previousPosition = initialPosition; - this.previousVelocity = initialVelocity; - this.previousVMach = 0.0; - this.lookAngle = 0.0; - } - - setupSeenZero(height: number, barrelElevation: number, lookAngle: number) { - if (height >= 0) { - this.seenZero |= TrajFlag.ZERO_UP; - } else if (height < 0 && barrelElevation < lookAngle) { - this.seenZero |= TrajFlag.ZERO_DOWN; - } - this.lookAngle = lookAngle; - } - - shouldRecord( - position: Vector, - velocity: Vector, - mach: number, - time: number, - ): BaseTrajectoryData | null { - let data: BaseTrajectoryData | null = null; - this.currentFlag = TrajFlag.NONE; - if (this.rangeStep > 0 && position.x >= this.nextRecordDistance) { - while (this.nextRecordDistance + this.rangeStep < position.x) { - this.nextRecordDistance += this.rangeStep; - } - if (position.x > this.previousPosition.x) { - const ratio = - (this.nextRecordDistance - this.previousPosition.x) / - (position.x - this.previousPosition.x); - data = { - time: - this.previousTime + (time - this.previousTime) * ratio, - position: this.previousPosition.add( - position - .subtract(this.previousPosition) - .mulByConst(ratio), - ), - velocity: this.previousVelocity.add( - velocity - .subtract(this.previousVelocity) - .mulByConst(ratio), - ), - mach: - this.previousMach + (mach - this.previousMach) * ratio, - }; - } - this.currentFlag |= TrajFlag.RANGE; - this.nextRecordDistance += this.rangeStep; - this.timeOfLastRecord = time; - } else if (this.timeStep > 0) { - this.checkNextTime(time); - } - this.checkZeroCrossing(position); - this.checkMachCrossing(velocity.magnitude(), mach); - - if (Boolean(this.currentFlag & this.filter) && data === null) { - data = { - time, - position, - velocity, - mach, - }; - } - this.previousTime = time; - this.previousPosition = position; - this.previousVelocity = velocity; - this.previousMach = mach; - return data; - } - - checkNextTime(time: number) { - if (time > this.timeOfLastRecord + this.timeStep) { - this.currentFlag |= TrajFlag.RANGE; - this.timeOfLastRecord = time; - } - } - - checkMachCrossing(velocity: number, mach: number) { - const currentVMach = velocity / mach; - if (this.previousVMach > 1 && 1 >= currentVMach) { - this.currentFlag |= TrajFlag.MACH; - } - this.previousVMach = currentVMach; - } - - checkZeroCrossing(rangeVector: Vector) { - if (rangeVector.x > 0) { - const referenceHeight = rangeVector.x * Math.tan(this.lookAngle); - - if (!(this.seenZero & TrajFlag.ZERO_UP)) { - if (rangeVector.y >= referenceHeight) { - this.currentFlag |= TrajFlag.ZERO_UP; - this.seenZero |= TrajFlag.ZERO_UP; - } - } else if (!(this.seenZero & TrajFlag.ZERO_DOWN)) { - if (rangeVector.y < referenceHeight) { - this.currentFlag |= TrajFlag.ZERO_DOWN; - this.seenZero |= TrajFlag.ZERO_DOWN; - } - } - } - } -} - -class _WindSock { - winds: Wind[]; - current: number; - nextRange: number; - protected _last_vector_cache: Vector | null; - protected _length: number; - - constructor(winds: Wind[] | null) { - this.winds = winds ?? []; - this.current = 0; - this.nextRange = Wind.MAX_DISTANCE_FEET; - this._last_vector_cache = null; - this._length = this.winds.length; - - this.updateCache(); - } - - currentVector(): Vector { - if (!this._last_vector_cache) { - throw new Error("No cached wind vector"); - } - return this._last_vector_cache; - } - - updateCache() { - if (this.current < this._length) { - const curWind = this.winds[this.current]; - this._last_vector_cache = curWind.vector; - this.nextRange = curWind.untilDistance.In(Distance.Foot); - } else { - this._last_vector_cache = new Vector(0.0, 0.0, 0.0); - this.nextRange = Wind.MAX_DISTANCE_FEET; - } - } - - vectorForRange(nextRange: number): Vector { - if (nextRange >= this.nextRange) { - this.current += 1; - if (this.current >= this._length) { - this._last_vector_cache = new Vector(0.0, 0.0, 0.0); - this.nextRange = Wind.MAX_DISTANCE_FEET; - } else { - this.updateCache(); - } - } - return this.currentVector(); - } -} - -const defaultEngineConfig: BaseEngineConfig = { - cMaxCalcStepSizeFeet, - cZeroFindingAccuracy, - cMinimumVelocity, - cMaximumDrop, - cMaxIterations, - cGravityConstant, - cMinimumAltitude, -}; - -class BaseIntegrationEngine implements EngineInterface { - gravityVector: Vector; - - protected _config: BaseEngineConfig; - - protected _bc: number; - protected _tableData: DragTable; - protected _curve: Curve; - private __mach_list: number[]; - - protected _tProps: BaseEngineTrajectoryProps; - - constructor(config: Partial) { - this._config = { ...defaultEngineConfig, ...config }; - this.gravityVector = new Vector( - 0.0, - this._config.cGravityConstant, - 0.0, - ); - } - - /** - * Retrieves the drag table data used in trajectory calculations. - * @returns {DragTable} The drag table data. - */ - get tableData(): DragTable { - return this._tableData; - } - - zeroAngle(shotInfo: Shot, distance: Distance): Angular { - this._initTrajectory(shotInfo); - - const { cZeroFindingAccuracy, cMaxIterations } = this._config; - const { lookAngle } = this._tProps; - - const distanceFeet = distance.In(Distance.Foot); - const zeroDistance = Math.cos(lookAngle) * distanceFeet; - const heightAtZero = Math.sin(lookAngle) * distanceFeet; - - let iterationsCount = 0; - let zeroFindingError = cZeroFindingAccuracy * 2; - - let height: number; - while ( - zeroFindingError > cZeroFindingAccuracy && - iterationsCount < cMaxIterations - ) { - try { - let t = this._integrate( - shotInfo, - zeroDistance, - zeroDistance, - TrajFlag.NONE, - )[0]; - height = t.height.In(Distance.Foot); - } catch (e: unknown) { - if (e instanceof TrajectoryRangeError) { - if ( - e.lastDistance === null || - e.lastDistance == undefined - ) { - throw e; - } - let lastDistanceFoot = e.lastDistance.In(Distance.Foot); - let proportion = lastDistanceFoot / zeroDistance; - height = - e.incompleteTrajectory[ - e.incompleteTrajectory.length - 1 - ].height.In(Distance.Foot) / proportion; - } else { - throw e; - } - } - - zeroFindingError = Math.abs(height - heightAtZero); - - if (zeroFindingError > cZeroFindingAccuracy) { - this._tProps.barrelElevation -= - (height - heightAtZero) / zeroDistance; - } else { - break; - } - iterationsCount += 1; - } - - if (zeroFindingError > cZeroFindingAccuracy) { - throw new ZeroFindingError( - zeroFindingError, - iterationsCount, - UNew.Radian(this._tProps.barrelElevation), - ); - } - return UNew.Radian(this._tProps.barrelElevation); - } - - trajectory( - shotInfo: Shot, - maxRange: Distance, - distStep: Distance, - extraData: boolean = false, - timeStep: number = 0.0, - ): TrajectoryData[] { - let filterFlags = TrajFlag.RANGE; - if (extraData) { - filterFlags = TrajFlag.ALL; - } - this._initTrajectory(shotInfo); - return this._integrate( - shotInfo, - maxRange.In(Distance.Foot), - distStep.In(Distance.Foot), - filterFlags, - timeStep, - ); - } - - /** - * Initializes the trajectory properties based on the provided shot information. - * @param {Shot} shotInfo - The shot information including weapon, ammo, and environmental conditions. - * @private - */ - protected _initTrajectory(shotInfo: Shot): void { - (this._bc = shotInfo.ammo.dm.bc), - (this._tableData = shotInfo.ammo.dm.dragTable); - this._curve = calculateCurve(this._tableData); - - this.__mach_list = getOnlyMachData(this._tableData); - - this._tProps = { - lookAngle: shotInfo.lookAngle.In(Angular.Radian), - twist: shotInfo.weapon.twist.In(Distance.Inch), - length: shotInfo.ammo.dm.length.In(Distance.Inch), - diameter: shotInfo.ammo.dm.diameter.In(Distance.Inch), - weight: shotInfo.ammo.dm.weight.In(Weight.Grain), - barrelElevation: shotInfo.barrelElevation.In(Angular.Radian), - barrelAzimuth: shotInfo.barrelAzimuth.In(Angular.Radian), - sightHeight: shotInfo.weapon.sightHeight.In(Distance.Foot), - cantCosine: Math.cos(shotInfo.cantAngle.In(Angular.Radian)), - cantSine: Math.sin(shotInfo.cantAngle.In(Angular.Radian)), - alt0: shotInfo.atmo.altitude.In(Distance.Foot), - calcStep: this.getCalcStep(), - muzzleVelocity: shotInfo.ammo - .getVelocityForTemp(shotInfo.atmo.powderTemp) - .In(Velocity.FPS), - stabilityCoefficient: 0, - }; - this._tProps.stabilityCoefficient = this.calcStabilityCoefficient( - shotInfo.atmo, - ); - } - - protected _integrate( - shotInfo: Shot, - maximumRange: number, - recordStep: number, - filterFlags: TrajFlag, - timeStep: number = 0.0, - ): TrajectoryData[] { - throw new Error("Not implemented"); - } - - /** - * Retrieves the calculation step size for trajectory calculations. - * @param {number} [step=0] - The step size to retrieve. - * @returns {number} The calculation step size. - */ - getCalcStep(step: number = 0): number { - const preferredStep = this._config.cMaxCalcStepSizeFeet; - - if (step === 0) { - return preferredStep / 2.0; - } - return Math.min(step, preferredStep) / 2.0; - } - - /** - * Calculates the drag coefficient for a given Mach number. - * @param {number} mach - The Mach number for which the drag coefficient is to be calculated. - * @returns {number} - The calculated drag coefficient for the provided Mach number. - */ - dragByMach(mach: number): number { - const cd = calculateByCurveAndMachList( - this.__mach_list, - this._curve, - mach, - ); // Assuming `calculateByCurve` exists - return (cd * 2.08551e-4) / this._bc; - } - - /** - * Calculates the spin drift of the projectile over time. - * @param {number} time - The time in seconds for which the spin drift is to be calculated. - * @returns {number} - The calculated spin drift in inches over the specified time. - */ - spinDrift(time: number): number { - if (this._tProps.twist !== 0) { - const sign = this._tProps.twist > 0 ? 1 : -1; - return ( - (sign * - (1.25 * - (this._tProps.stabilityCoefficient + 1.2) * - Math.pow(time, 1.83))) / - 12 - ); - } - return 0; - } - - /** - * Calculates the stability coefficient of the projectile based on atmospheric conditions. - * @param {Atmo} atmo - The atmospheric conditions including temperature, pressure, and humidity. - * @returns {number} - The calculated stability coefficient. - */ - calcStabilityCoefficient(atmo: Atmo): number { - const { twist, length, weight, diameter, muzzleVelocity } = - this._tProps; - if (twist && length && diameter && atmo.pressure.rawValue) { - const twistRate = Math.abs(twist) / diameter; - const lengthRatio = length / diameter; - - // Miller stability formula - const sd = - (30 * weight) / - (Math.pow(twistRate, 2) * - Math.pow(diameter, 3) * - lengthRatio * - (1 + Math.pow(lengthRatio, 2))); - - // Velocity correction factor - const fv = Math.pow(muzzleVelocity / 2800, 1.0 / 3.0); - - // Atmospheric correction - const ft = atmo.temperature.In(Temperature.Fahrenheit); // Assuming a method to convert to Fahrenheit - const pt = atmo.pressure.In(Pressure.InHg); // Assuming a method to convert to InHg - const ftp = ((ft + 460) / (59 + 460)) * (29.92 / pt); - - return sd * fv * ftp; - } - return 0; - } -} - -/** - * Creates a trajectory data row for a given time step. - * @param {number} time - The time at which the trajectory data is calculated. - * @param {Vector} rangeVector - The vector representing the range. - * @param {Vector} velocityVector - The vector representing the velocity. - * @param {number} velocity - The magnitude of the velocity. - * @param {number} mach - The Mach number corresponding to the velocity. - * @param {number} spinDrift - The spin drift effect on the trajectory. - * @param {number} lookAngle - The angle of the sight relative to the bore axis. - * @param {number} densityFactor - The atmospheric density factor affecting the trajectory. - * @param {number} drag - The drag force experienced by the projectile. - * @param {number} weight - The weight of the projectile. - * @param {number} flag - Flags indicating specific trajectory conditions or calculations. - * @returns {TrajectoryData} - An object containing the calculated trajectory data. - */ -const createTrajectoryRow = ( - time: number, - rangeVector: Vector, - velocityVector: Vector, - velocity: number, - mach: number, - spinDrift: number, - lookAngle: number, - densityFactor: number, - drag: number, - weight: number, - flag: number, -): TrajectoryData => { - const windage = rangeVector.z + spinDrift; - const dropAdjustment = getCorrection(rangeVector.x, rangeVector.y); - const windageAdjustment = getCorrection(rangeVector.x, windage); - const trajectoryAngle = Math.atan(velocityVector.y / velocityVector.x); - - return new TrajectoryData( - time, - UNew.Foot(rangeVector.x), - UNew.FPS(velocity), - velocity / mach, - UNew.Foot(rangeVector.y), - UNew.Foot( - (rangeVector.y - rangeVector.x * Math.tan(lookAngle)) * - Math.cos(lookAngle), - ), - UNew.Radian(dropAdjustment - (rangeVector.x ? lookAngle : 0)), - UNew.Foot(windage), - UNew.Radian(windageAdjustment), - UNew.Foot(rangeVector.x / Math.cos(lookAngle)), - UNew.Radian(trajectoryAngle), - densityFactor - 1, - drag, - UNew.FootPound(calculateEnergy(weight, velocity)), - UNew.Pound(calculateOGW(weight, velocity)), - flag, - ); -}; - -/** - * Calculates the correction needed for a given distance and offset. - * @param {number} distance - The distance for which the correction is to be calculated. - * @param {number} offset - The offset that affects the correction calculation. - * @returns {number} - The calculated correction value. - */ -const getCorrection = (distance: number, offset: number): number => { - // Sight adjustment in radians - if (distance != 0) { - return Math.atan(offset / distance); - } - return 0; -}; - -/** - * Calculates the kinetic energy of a bullet based on its weight and velocity. - * @param {number} bulletWeight - The weight of the bullet in grains. - * @param {number} velocity - The velocity of the bullet in feet per second (FPS). - * @returns {number} - The calculated kinetic energy in foot-pounds (ft-lb). - */ -const calculateEnergy = (bulletWeight: number, velocity: number): number => { - // energy in ft-lbs - return (bulletWeight * Math.pow(velocity, 2)) / 450400; -}; - -/** - * Calculates the optical ground weight (OGW) of a bullet based on its weight and velocity. - * @param {number} bulletWeight - The weight of the bullet in grains. - * @param {number} velocity - The velocity of the bullet in feet per second (FPS). - * @returns {number} - The calculated optical ground weight. - */ -const calculateOGW = (bulletWeight: number, velocity: number): number => { - // Optimal Game Weight in pounds - return Math.pow(bulletWeight, 2) * Math.pow(velocity, 3) * 1.5e-12; -}; - -/** - * Calculates a curve based on drag data points for trajectory analysis. - * @param {DragTable} dataPoints - An array of `DragDataPoint` objects representing the drag data. - * @returns {Curve} - An array of `CurvePoint` objects representing the calculated curve. - */ -const calculateCurve = (dataPoints: DragTable): Curve => { - let rate: number = - (dataPoints[1].CD - dataPoints[0].CD) / - (dataPoints[1].Mach - dataPoints[0].Mach); - let curve: Curve = [ - { - a: 0, - b: rate, - c: dataPoints[0].CD - dataPoints[0].Mach * rate, - }, - ]; - const lenDataPoints: number = dataPoints.length; - const lenDataRange: number = lenDataPoints - 1; - - let x1, x2, x3, y1, y2, y3, a, b, c: number; - - for (let i: number = 1; i < lenDataRange; i++) { - x1 = dataPoints[i - 1].Mach; - x2 = dataPoints[i].Mach; - x3 = dataPoints[i + 1].Mach; - y1 = dataPoints[i - 1].CD; - y2 = dataPoints[i].CD; - y3 = dataPoints[i + 1].CD; - a = - ((y3 - y1) * (x2 - x1) - (y2 - y1) * (x3 - x1)) / - ((x3 * x3 - x1 * x1) * (x2 - x1) - (x2 * x2 - x1 * x1) * (x3 - x1)); - b = (y2 - y1 - a * (x2 * x2 - x1 * x1)) / (x2 - x1); - c = y1 - (a * x1 * x1 + b * x1); - curve.push({ a, b, c }); - } - - let numPoints: number = lenDataPoints; - rate = - (dataPoints[numPoints - 1].CD - dataPoints[numPoints - 2].CD) / - (dataPoints[numPoints - 1].CD - dataPoints[numPoints - 2].Mach); - curve.push({ - a: 0, - b: rate, - c: dataPoints[numPoints - 1].CD - dataPoints[numPoints - 2].Mach * rate, - }); - return curve; -}; - -// Extracts Mach values from a list of DragDataPoint objects. -const getOnlyMachData = (data: DragTable): number[] => { - return data.map((item) => item.Mach); -}; - -const calculateByCurveAndMachList = ( - machList: number[], - curve: Curve, - mach: number, -) => { - let m: number = 0; - let mid: number = 0; - let mlo: number = 0; - let mhi: number = curve.length - 2; - - while (mhi - mlo > 1) { - mid = Math.round((mhi + mlo) / 2.0); - if (machList[mid] < mach) { - mlo = mid; - } else { - mhi = mid; - } - - if (machList[mhi] - mach > mach - machList[mlo]) { - m = mlo; - } else { - m = mhi; - } - } - const curveM: CurvePoint = curve[m]; - return curveM.c + mach * (curveM.b + curveM.a * mach); -}; diff --git a/src/engines/euler.ts b/src/engines/euler.ts deleted file mode 100644 index 2000715..0000000 --- a/src/engines/euler.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { Shot } from "../conditions"; -import { TrajectoryData, TrajFlag } from "../trajectory_data"; -import Vector from "../vector"; -import { TrajectoryRangeError } from "../exceptions"; -import { EngineInterface } from "../generics/engine"; -import { - BaseEngineConfig, - BaseIntegrationEngine, - createTrajectoryRow, - _TrajectoryDataFilter, - _WindSock, -} from "./base_engine"; - -class EulerIntegrationEngine - extends BaseIntegrationEngine - implements EngineInterface -{ - protected _integrate( - shotInfo: Shot, - maximumRange: number, - recordStep: number, - filterFlags: TrajFlag, - timeStep: number = 0.0, - ): TrajectoryData[] { - const { cMinimumVelocity, cMaximumDrop, cMinimumAltitude } = - this._config; - - const { - muzzleVelocity, - cantCosine, - sightHeight, - cantSine, - barrelElevation, - barrelAzimuth, - calcStep, - alt0, - lookAngle, - weight, - } = this._tProps; - let ranges: TrajectoryData[] = []; - let time: number = 0.0; - let drag: number = 0.0; - - let mach: number = 0.0; - let densityFactor: number = 0.0; - - const windSock = new _WindSock(shotInfo.winds); - let windVector = windSock.currentVector(); - - let velocity: number = muzzleVelocity; - let rangeVector: Vector = new Vector( - 0.0, - -cantCosine * sightHeight, - -cantSine * sightHeight, - ); - let velocityVector: Vector = new Vector( - Math.cos(barrelElevation) * Math.cos(barrelAzimuth), - Math.sin(barrelElevation), - Math.cos(barrelElevation) * Math.sin(barrelAzimuth), - ).mulByConst(velocity); - - const minStep = Math.min(calcStep, recordStep); - const dataFilter = new _TrajectoryDataFilter( - filterFlags, - recordStep, - rangeVector, - velocityVector, - timeStep, - ); - dataFilter.setupSeenZero(rangeVector.y, barrelElevation, lookAngle); - - let lastRecordedRange = 0.0; - while ( - rangeVector.x <= maximumRange + minStep || - (filterFlags && lastRecordedRange <= maximumRange - 1e-6) - ) { - if (rangeVector.x >= windSock.nextRange) { - windVector = windSock.vectorForRange(rangeVector.x); - } - - [densityFactor, mach] = - shotInfo.atmo.getDensityFactorAndMachForAltitude( - alt0 + rangeVector.y, - ); - - if (filterFlags) { - const data = dataFilter.shouldRecord( - rangeVector, - velocityVector, - mach, - time, - ); - if (data) { - ranges.push( - createTrajectoryRow( - data.time, - data.position, - data.velocity, - data.velocity.magnitude(), - data.mach, - this.spinDrift(data.time), - lookAngle, - densityFactor, - drag, - weight, - dataFilter.currentFlag, - ), - ); - lastRecordedRange = data.position.x; - } - } - - const velocityAdjusted = velocityVector.subtract(windVector); - velocity = velocityAdjusted.magnitude(); - const deltaTime = calcStep / Math.max(1.0, velocity); - drag = densityFactor * velocity * this.dragByMach(velocity / mach); - velocityVector = velocityVector.subtract( - velocityAdjusted - .mulByConst(drag) - .subtract(this.gravityVector) - .mulByConst(deltaTime), - ); - const deltaRangeVector = velocityVector.mulByConst(deltaTime); - rangeVector = rangeVector.add(deltaRangeVector); - velocity = velocityVector.magnitude(); - time += deltaTime; - - if ( - velocity < cMinimumVelocity || - rangeVector.y < cMaximumDrop || - alt0 + rangeVector.y < cMinimumAltitude - ) { - ranges.push( - createTrajectoryRow( - time, - rangeVector, - velocityVector, - velocity, - mach, - this.spinDrift(time), - lookAngle, - densityFactor, - drag, - weight, - dataFilter.currentFlag, - ), - ); - - let reason = ""; - if (velocity < cMinimumVelocity) { - reason = TrajectoryRangeError.MinimumVelocityReached; - } else if (rangeVector.y < cMaximumDrop) { - reason = TrajectoryRangeError.MaximumDropReached; - } else { - reason = TrajectoryRangeError.MinimumAltitudeReached; - } - throw new TrajectoryRangeError(reason, ranges); - } - } - - if (ranges.length < 2) { - ranges.push( - createTrajectoryRow( - time, - rangeVector, - velocityVector, - velocity, - mach, - this.spinDrift(time), - lookAngle, - densityFactor, - drag, - weight, - TrajFlag.NONE, - ), - ); - } - return ranges; - } -} - -export default EulerIntegrationEngine; diff --git a/src/engines/index.ts b/src/engines/index.ts deleted file mode 100644 index 5b02d1f..0000000 --- a/src/engines/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - createTrajectoryRow, - BaseEngineConfig, - BaseTrajectoryData, - BaseEngineTrajectoryProps, - BaseIntegrationEngine, - defaultEngineConfig, - calculateEnergy, - calculateOGW, - getCorrection, - _WindSock, - _TrajectoryDataFilter, - Curve, - CurvePoint, -} from "./base_engine"; -import EulerIntegrationEngine from "./euler"; -import RK4IntegrationEngine from "./rk4"; - -export { - createTrajectoryRow, - BaseEngineConfig, - BaseTrajectoryData, - BaseEngineTrajectoryProps, - BaseIntegrationEngine, - EulerIntegrationEngine, - RK4IntegrationEngine, - defaultEngineConfig, - calculateEnergy, - calculateOGW, - getCorrection, - _WindSock, - _TrajectoryDataFilter, - Curve, - CurvePoint, -}; diff --git a/src/engines/rk4.ts b/src/engines/rk4.ts deleted file mode 100644 index 5287a45..0000000 --- a/src/engines/rk4.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { Shot } from "../conditions"; -import { TrajectoryData, TrajFlag } from "../trajectory_data"; -import Vector from "../vector"; -import { TrajectoryRangeError } from "../exceptions"; -import { EngineInterface } from "../generics/engine"; -import { - BaseEngineConfig, - BaseIntegrationEngine, - createTrajectoryRow, - _TrajectoryDataFilter, - _WindSock, -} from "./base_engine"; - -class RK4IntegrationEngine - extends BaseIntegrationEngine - implements EngineInterface { - - - /** - * Retrieves the calculation step size for trajectory calculations. - * @param {number} [step=0] - The step size to retrieve. - * @returns {number} The calculation step size. - */ - getCalcStep(step: number = 0): number { - return Math.pow(super.getCalcStep(step), 0.5); - } - - protected _integrate( - shotInfo: Shot, - maximumRange: number, - recordStep: number, - filterFlags: TrajFlag, - timeStep: number = 0.0, - ): TrajectoryData[] { - const { cMinimumVelocity, cMaximumDrop, cMinimumAltitude } = - this._config; - - const { - muzzleVelocity, - cantCosine, - sightHeight, - cantSine, - barrelElevation, - barrelAzimuth, - calcStep, - alt0, - lookAngle, - weight, - } = this._tProps; - let ranges: TrajectoryData[] = []; - let time: number = 0.0; - let drag: number = 0.0; - - let mach: number = 0.0; - let densityFactor: number = 0.0; - - const windSock = new _WindSock(shotInfo.winds); - let windVector = windSock.currentVector(); - - let velocity: number = muzzleVelocity; - let rangeVector: Vector = new Vector( - 0.0, - -cantCosine * sightHeight, - -cantSine * sightHeight, - ); - let velocityVector: Vector = new Vector( - Math.cos(barrelElevation) * Math.cos(barrelAzimuth), - Math.sin(barrelElevation), - Math.cos(barrelElevation) * Math.sin(barrelAzimuth), - ).mulByConst(velocity); - - const minStep = Math.min(calcStep, recordStep); - - const dataFilter = new _TrajectoryDataFilter( - filterFlags, - recordStep, - rangeVector, - velocityVector, - timeStep, - ); - dataFilter.setupSeenZero(rangeVector.y, barrelElevation, lookAngle); - - let lastRecordedRange = 0.0; - while ( - rangeVector.x <= maximumRange + minStep || - (filterFlags && lastRecordedRange <= maximumRange - 1e-6) - ) { - if (rangeVector.x >= windSock.nextRange) { - windVector = windSock.vectorForRange(rangeVector.x); - } - - [densityFactor, mach] = - shotInfo.atmo.getDensityFactorAndMachForAltitude( - alt0 + rangeVector.y, - ); - - if (filterFlags) { - const data = dataFilter.shouldRecord( - rangeVector, - velocityVector, - mach, - time, - ); - if (data) { - ranges.push( - createTrajectoryRow( - data.time, - data.position, - data.velocity, - data.velocity.magnitude(), - data.mach, - this.spinDrift(data.time), - lookAngle, - densityFactor, - drag, - weight, - dataFilter.currentFlag, - ), - ); - lastRecordedRange = data.position.x; - } - } - - const relativeVelocity = velocityVector.subtract(windVector); - const relativeSpeed = relativeVelocity.magnitude(); - const deltaTime = calcStep / Math.max(1.0, relativeSpeed); - const km = densityFactor * this.dragByMach(relativeSpeed / mach); - drag = km * relativeSpeed; - - const f = (v: Vector): Vector => { - return this.gravityVector.subtract( - v.mulByConst(km).mulByConst(v.magnitude()), - ); - }; - - const v1: Vector = f(relativeVelocity).mulByConst(deltaTime); - const v2: Vector = f(relativeVelocity.add(v1.mulByConst(0.5))).mulByConst(deltaTime); - const v3: Vector = f(relativeVelocity.add(v2.mulByConst(0.5))).mulByConst(deltaTime); - const v4: Vector = f(relativeVelocity.add(v3)).mulByConst(deltaTime); - const p1: Vector = velocityVector.mulByConst(deltaTime); - const p2: Vector = velocityVector.add(p1.mulByConst(0.5)).mulByConst(deltaTime); - const p3: Vector = velocityVector.add(p2.mulByConst(0.5)).mulByConst(deltaTime); - const p4: Vector = velocityVector.add(p3).mulByConst(deltaTime); - - velocityVector = velocityVector.add( - Vector.sum( - v1, - v2.mulByConst(2), - v3.mulByConst(2), - v4, - ).mulByConst(1 / 6.0), - ); - - rangeVector = rangeVector.add( - Vector.sum( - p1, - p2.mulByConst(2), - p3.mulByConst(2), - p4, - ).mulByConst(1 / 6.0), - ); - - velocity = velocityVector.magnitude(); - time += deltaTime; - - if ( - velocity < cMinimumVelocity || - rangeVector.y < cMaximumDrop || - alt0 + rangeVector.y < cMinimumAltitude - ) { - ranges.push( - createTrajectoryRow( - time, - rangeVector, - velocityVector, - velocity, - mach, - this.spinDrift(time), - lookAngle, - densityFactor, - drag, - weight, - dataFilter.currentFlag, - ), - ); - - let reason = ""; - if (velocity < cMinimumVelocity) { - reason = TrajectoryRangeError.MinimumVelocityReached; - } else if (rangeVector.y < cMaximumDrop) { - reason = TrajectoryRangeError.MaximumDropReached; - } else { - reason = TrajectoryRangeError.MinimumAltitudeReached; - } - throw new TrajectoryRangeError(reason, ranges); - } - } - - if (ranges.length < 2) { - ranges.push( - createTrajectoryRow( - time, - rangeVector, - velocityVector, - velocity, - mach, - this.spinDrift(time), - lookAngle, - densityFactor, - drag, - weight, - TrajFlag.NONE, - ), - ); - } - return ranges; - } -} - -export default RK4IntegrationEngine; diff --git a/src/exceptions.ts b/src/exceptions.ts index 68a4067..1a34241 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -1,23 +1,49 @@ -import { Angular, Distance } from "./unit"; // Assuming 'unit.ts' exists -import { TrajectoryData } from "./trajectory_data"; // Assuming 'trajectory_data.ts' exists +/** + * py_ballisticcalc exception types for TypeScript + * + * This module provides a comprehensive exception hierarchy for handling various error conditions + * that can occur during ballistic calculations. + * + * Exception Hierarchy + * ------------------- + * + * Exception (built-in) + * ├── TypeError + * │ └── UnitTypeError + * │ └── UnitConversionError + * ├── ValueError + * │ └── UnitAliasError + * └── SolverRuntimeError + * ├── ZeroFindingError + * ├── RangeError + * ├── OutOfRangeError + * └── InterceptionError + */ + +import { Angular, Distance } from "./unit"; +import { TrajectoryData } from "./trajectory_data"; +import { BaseTrajData } from "./_wasm"; export { - ValueError, UnitTypeError, UnitConversionError, UnitAliasError, + SolverRuntimeError, ZeroFindingError, - TrajectoryRangeError, + RangeError, + OutOfRangeError, + InterceptionError, }; -// You might need a base ValueError if you don't have one, or extend from a standard Error -class ValueError extends Error { - constructor(message?: string) { - super(message); - this.name = "ValueError"; - } -} +// ============================================================================ +// Unit-Related Exceptions +// ============================================================================ +/** + * Base class for unit-related type errors. + * Raised when invalid unit types are passed to unit conversion functions + * or there are type mismatches in unit operations. + */ class UnitTypeError extends TypeError { constructor(message?: string) { super(message); @@ -25,6 +51,11 @@ class UnitTypeError extends TypeError { } } +/** + * Raised when unit conversion fails. + * Occurs when attempting to convert between incompatible unit types + * or when a unit is not supported in the conversion factor table. + */ class UnitConversionError extends UnitTypeError { constructor(message?: string) { super(message); @@ -32,58 +63,162 @@ class UnitConversionError extends UnitTypeError { } } -class UnitAliasError extends ValueError { - // Assuming ValueError is a base custom error or standard JS Error +/** + * Raised when unit alias parsing fails. + * Occurs when invalid unit alias strings are provided + * or when there are ambiguous unit abbreviations. + */ +class UnitAliasError extends Error { constructor(message?: string) { super(message); this.name = "UnitAliasError"; } } -class ZeroFindingError extends Error { +// ============================================================================ +// Solver-Related Exceptions +// ============================================================================ + +/** + * Base class for all solver-related runtime errors. + * This is the base class for all ballistic calculation errors + * and is typically not raised directly. + */ +class SolverRuntimeError extends Error { + constructor(message?: string) { + super(message); + this.name = "SolverRuntimeError"; + } +} + +/** + * Raised when zero-finding algorithms fail to converge. + */ +class ZeroFindingError extends SolverRuntimeError { + public static readonly DISTANCE_NON_CONVERGENT = "Distance non-convergent"; + public static readonly ERROR_NON_CONVERGENT = "Error non-convergent"; + public zeroFindingError: number; public iterationsCount: number; public lastBarrelElevation: Angular; + public reason: string; + /** + * @param zeroFindingError - Error magnitude in feet + * @param iterationsCount - Number of iterations performed + * @param lastBarrelElevation - Last computed barrel elevation (Angular instance) + * @param reason - Specific reason for failure + */ constructor( zeroFindingError: number, iterationsCount: number, lastBarrelElevation: Angular, + reason: string = "" ) { - super( - `Zero vertical error ${zeroFindingError} feet, after ${iterationsCount} iterations.`, - ); + let message = `Vertical error ${zeroFindingError} feet with ${lastBarrelElevation} elevation, after ${iterationsCount} iterations.`; + + if (reason) { + message = `${reason}. ${message}`; + } + + super(message); this.name = "ZeroFindingError"; this.zeroFindingError = zeroFindingError; this.iterationsCount = iterationsCount; this.lastBarrelElevation = lastBarrelElevation; + this.reason = reason; } } -class TrajectoryRangeError extends Error { +/** + * Raised when trajectory doesn't reach the requested distance. + */ +class RangeError extends SolverRuntimeError { + public static readonly MinimumVelocityReached = "Minimum velocity reached"; + public static readonly MaximumDropReached = "Maximum drop reached"; + public static readonly MinimumAltitudeReached = "Minimum altitude reached"; + public reason: string; public incompleteTrajectory: TrajectoryData[]; public lastDistance: Distance | null; - public static readonly MinimumVelocityReached: string = - "Minimum velocity reached"; - public static readonly MaximumDropReached: string = "Maximum drop reached"; - public static readonly MinimumAltitudeReached: string = - "Minimum altitude reached"; - - constructor(reason: string, trajectory: TrajectoryData[]) { + /** + * @param reason - The error reason + * @param ranges - The trajectory data before the exception occurred + */ + constructor(reason: string, ranges: TrajectoryData[]) { let message = `Max range not reached: (${reason})`; let lastDistance: Distance | null = null; - if (trajectory.length > 0) { - lastDistance = trajectory[trajectory.length - 1].distance; + if (ranges.length > 0) { + lastDistance = ranges[ranges.length - 1].distance; message += `, last distance: ${lastDistance}`; } super(message); this.name = "RangeError"; this.reason = reason; - this.incompleteTrajectory = trajectory; + this.incompleteTrajectory = ranges; this.lastDistance = lastDistance; } } + +/** + * Raised when requested distance exceeds maximum possible range. + */ +class OutOfRangeError extends SolverRuntimeError { + public requestedDistance: Distance; + public maxRange?: Distance; + public lookAngle?: Angular; + + /** + * @param requestedDistance - The distance that was requested + * @param maxRange - Maximum achievable range (optional) + * @param lookAngle - Look angle for the shot (optional) + * @param note - Additional note (optional) + */ + constructor( + requestedDistance: Distance, + maxRange?: Distance, + lookAngle?: Angular, + note: string = "" + ) { + let message = `Requested distance ${requestedDistance}`; + + if (maxRange !== undefined) { + message += ` exceeds maximum possible range ${maxRange.foot} feet`; + } + + if (lookAngle !== undefined && lookAngle.rawValue) { + message += ` with look-angle ${lookAngle.rad} rad`; + } + + if (note) { + message += `. ${note}`; + } + + super(message); + this.name = "OutOfRangeError"; + this.requestedDistance = requestedDistance; + this.maxRange = maxRange; + this.lookAngle = lookAngle; + } +} + +/** + * Raised when interpolation can't find interception point + * for target key and value during integration. + */ +class InterceptionError extends SolverRuntimeError { + public lastData: [BaseTrajData, TrajectoryData]; + + /** + * @param message - Error message + * @param lastData - Tuple of [BaseTrajData, TrajectoryData] + */ + constructor(message: string, lastData: [BaseTrajData, TrajectoryData]) { + super(message); + this.name = "InterceptionError"; + this.lastData = lastData; + } +} diff --git a/src/generics/engine.ts b/src/generics/engine.ts deleted file mode 100644 index 3aefe15..0000000 --- a/src/generics/engine.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Shot } from "../conditions"; -import { DragTable } from "../drag_model"; -import { TrajectoryData } from "../trajectory_data"; -import { Angular, Distance } from "../unit"; - -interface GenericConfig { - // This could be any generic configuration structure -} - -interface EngineConstructor { - new (config: Partial): EngineInterface; -} - -interface EngineInterface { - readonly tableData: DragTable; - zeroAngle(shotInfo: Shot, distance: Distance): Angular; - trajectory( - shotInfo: Shot, - maxRange: Distance, - distStep: Distance, - extraData?: boolean, - timeStep?: number, - ): TrajectoryData[]; -} - -function createEngine( - ctor: EngineConstructor, - config: Partial, -): EngineInterface { - return new ctor(config); -} - -export { GenericConfig, EngineConstructor, EngineInterface, createEngine }; diff --git a/src/generics/index.ts b/src/generics/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/helpers.ts b/src/helpers.ts deleted file mode 100644 index b182ffb..0000000 --- a/src/helpers.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HitResult, TrajectoryData, TrajFlag } from "./trajectory_data"; -import { Angular, Distance, UNew, Unit, Velocity } from "./unit"; - -export const EARTH_GRAVITY_CONSTANT_IN_SI: number = 9.81; // Acceleration due to gravity (m/s^2) - -// Compute the maximal range for the projectile launched with `velocity_mps` with angle `angle_in_degrees`. -// The result assumes absence of drag force - so this distance will definitely overestimate (ca 4 times) -// the range in presence of drag force. -export const calculateDragFreeRange = ( - velocityMPS: number, - angleDegree: number, - gravity: number = EARTH_GRAVITY_CONSTANT_IN_SI, -): number => { - const angleRad = UNew.Degree(angleDegree).In(Angular.Radian); - return (Math.pow(velocityMPS, 2) * Math.sin(2 * angleDegree)) / gravity; -}; - -// Search sequentially for the index of first point in the trajectory, which matches condition. -export const findFirstIndexMatchingCondition = ( - hit: HitResult, - condition: (point: TrajectoryData) => number, -): number => { - hit.trajectory.forEach((point, i) => { - if (condition(point)) { - return i; - } - }); - return -1; -}; - -// Find index of first point, for which `flag` is set. -export const findIndexOfPointWithFlag = ( - hit: HitResult, - flag: TrajFlag = TrajFlag.ZERO_DOWN, -): number => { - return findFirstIndexMatchingCondition( - hit, - (p: TrajectoryData) => p.flag & flag, - ); -}; - -// Find index of point for which TrjFlag.MACH was set. -// Note - this requires calling calculator with extra_data=True. -export const findMachPointIndex = (hit: HitResult): number => { - return findIndexOfPointWithFlag(hit, TrajFlag.MACH); -}; - -// Find index of point when earth was hit by the bullet. -// Note - this requires calling calculator with extra_data=True. -export const findTouchPointIndex = ( - hit: HitResult, - velocityInUnits: number, - velocityUnit: Unit = Unit.MPS, -): number => { - return findFirstIndexMatchingCondition(hit, (p: TrajectoryData): number => - Number(p.velocity.In(velocityUnit) < velocityInUnits), - ); -}; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..3af1270 --- /dev/null +++ b/src/index.html @@ -0,0 +1,517 @@ + + + + + + + Ballistics Calculator (WASM) + + + + +
+

🎯 Ballistics Calculator (WASM)

+ +
+ +
+

Input Parameters

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ +
+ +
+

Results

+
Enter parameters and click Calculate...
+
+
+
+
+ + + + + + diff --git a/src/index.ts b/src/index.ts index 81f4c84..a83c647 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,121 +1,35 @@ -import { Atmo, Vacuum, Wind, Shot } from "./conditions"; -import { - DragModel, - DragDataPoint, - BCPoint, - DragModelMultiBC, - DragTable, - DragTableDataType, - Table, -} from "./drag_model"; -import { - _TrajectoryDataFilter, - _WindSock, - BaseEngineConfig, - BaseEngineTrajectoryProps, - BaseIntegrationEngine, - BaseTrajectoryData, - calculateEnergy, - calculateOGW, - createTrajectoryRow, - Curve, - CurvePoint, - defaultEngineConfig, - EulerIntegrationEngine, - RK4IntegrationEngine, - getCorrection, -} from "./engines"; -import { - ValueError, - UnitTypeError, - UnitConversionError, - UnitAliasError, - ZeroFindingError, - TrajectoryRangeError, -} from "./exceptions"; -import Calculator from "./interface"; -import { Weapon, Ammo } from "./munition"; -import { - TrajectoryData, - TrajFlag, - trajFlagName, - trajFlagNames, - DangerSpace, - HitResult, -} from "./trajectory_data"; -import { - AbstractUnit, - Angular, - Distance, - Velocity, - Weight, - Temperature, - Pressure, - Energy, - Unit, - UnitProps, - unitTypeCoerce, - UNew, - Measure, - preferredUnits, -} from "./unit"; -import Vector from "./vector"; +export type * from "./vector"; +export * from "./vector"; -export { - Atmo, - Vacuum, - Wind, - Shot, - createTrajectoryRow, - BaseEngineConfig, - BaseTrajectoryData, - BaseEngineTrajectoryProps, - BaseIntegrationEngine, - EulerIntegrationEngine, - RK4IntegrationEngine, - defaultEngineConfig, - calculateEnergy, - calculateOGW, - getCorrection, - _WindSock, - _TrajectoryDataFilter, - Curve, - CurvePoint, - Table, - DragModel, - DragTable, - DragTableDataType, - DragDataPoint, - BCPoint, - DragModelMultiBC, - ValueError, - UnitTypeError, - UnitConversionError, - UnitAliasError, - ZeroFindingError, - TrajectoryRangeError, - Calculator, - Weapon, - Ammo, - TrajectoryData, - TrajFlag, - trajFlagName, - trajFlagNames, - DangerSpace, - HitResult, - AbstractUnit, - Angular, - Distance, - Velocity, - Weight, - Temperature, - Pressure, - Energy, - Unit, - UnitProps, - unitTypeCoerce, - UNew, - Measure, - preferredUnits, - Vector, -}; +export type * from "./constants"; +export * from "./constants"; + +export type * from "./_wasm"; +export * from "./_wasm"; + +export type * from "./conditions"; +export * from "./conditions"; + +export type * from "./drag_tables"; +export * from "./drag_tables"; + +export type * from "./drag_model"; +export * from "./drag_model"; + +export type * from "./exceptions"; +export * from "./exceptions"; + +export type * from "./munition"; +export * from "./munition"; + +export type * from "./trajectory_data"; +export * from "./trajectory_data"; + +export type * from "./unit"; +export * from "./unit"; + +export type * from "./shot"; +export * from "./shot"; + +export type * from "./interface"; +export * from "./interface"; diff --git a/src/interface.ts b/src/interface.ts index 85a6c83..10dddb2 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,155 +1,248 @@ -import EulerIntegrationEngine from "./engines/euler"; -import { Shot } from "./conditions"; -import { DragTable } from "./drag_model"; -import { HitResult } from "./trajectory_data"; -import { - UNew, - Angular, - Distance, - unitTypeCoerce, - preferredUnits, -} from "./unit"; +import { Shot } from "./shot"; +import { UNew, Angular, Distance, unitTypeCoerce, preferredUnits } from "./unit"; import { - createEngine, - EngineConstructor, - EngineInterface, - GenericConfig, -} from "./generics/engine"; -import { BaseEngineConfig } from "./engines"; + WasmManager, + ShotPropsInput, + Config, + IntegrationMethod, + TrajectoryRequest, + _TrajFlag, + HitOutput, + TrajFlag, +} from "./_wasm"; +import { HitResult } from "./trajectory_data"; +import { cGravityImperial } from "./constants"; + +export { Calculator }; + +/** Maximum allowed slant-error in feet to end zero search */ +export const cZeroFindingAccuracy = 0.000005; + +/** Maximum number of iterations for zero search */ +export const cMaxIterations = 40; + +/** Minimum altitude in feet (below sea level) */ +export const cMinimumAltitude = -1500; + +/** Maximum drop from muzzle in feet to continue trajectory */ +export const cMaximumDrop = -10000; + +/** Minimum velocity in fps to continue trajectory */ +export const cMinimumVelocity = 50.0; + +/** Gravity constant in feet per second squared */ +export const cGravityConstant = -cGravityImperial; + +/** Multiplier for engine's default step (changes integration speed & precision) */ +export const cStepMultiplier = 1.0; + +/** Default configuration for shot calculations */ +export const DEFAULT_CONFIG: Config = { + zeroFindingAccuracy: cZeroFindingAccuracy, + maxIterations: cMaxIterations, + minimumAltitude: cMinimumAltitude, + maximumDrop: cMaximumDrop, + minimumVelocity: cMinimumVelocity, + gravityConstant: cGravityConstant, + stepMultiplier: cStepMultiplier, +}; /** - * A class for performing calculations related to trajectories. - * @template C The specific configuration type for the engine used by this calculator. + * A class for performing ballistic trajectory calculations. + * + * All methods automatically initialize the WASM module on first call, + * so no manual initialization is required. + * + * @example + * ```typescript + * // Simple usage - no initialization needed + * const calc = new Calculator(); + * const elevation = await calc.barrelElevationForTarget(shot, 1000); + * + * // With custom configuration + * const calc = new Calculator({ + * method: { value: 1 }, + * config: { maximumDrop: 1000 } + * }); + * ``` */ -class Calculator { - // Default to TrajectoryCalcConfig - - // The _config property should match the generic type C - protected _config: Partial; - // The engine constructor should be capable of creating an EngineInterface (engine constructor) - protected _engine: EngineConstructor; - // The actual instantiated engine (engine instance) - protected _calc: EngineInterface; +class Calculator { + public method: IntegrationMethod; + public config: Config; /** * Creates an instance of Calculator. - * @param options An object containing the configuration and an optional engine constructor. - * @param options.config The configuration for the engine. - * @param options.engine The constructor of the engine class to use (defaults to EulerIntegrationEngine). + * + * @param options Configuration options + * @param options.method The integration method to use + * @param options.config The calculation configuration */ - constructor(options?: { - config?: Partial; - engine?: EngineConstructor; - }) { - // Assign config directly. It's required, not optional, for type safety. + constructor(options?: Partial>) { options = options ?? {}; - this._config = options.config ?? {}; - - if (options.engine) { - // Case 1: An explicit engine constructor is provided. - this._engine = options.engine; - this._calc = createEngine(this._engine, this._config); - } else { - this._engine = - EulerIntegrationEngine as EngineConstructor; - this._calc = new EulerIntegrationEngine( - this._config as Partial, - ); - } - } + this.method = options.method ?? IntegrationMethod.RK4; + this.config = { ...DEFAULT_CONFIG, ...(options.config ?? {}) }; - /** - * Retrieves the drag table data from the trajectory calculations. - * @returns {DragTable} - The drag table data used in the trajectory calculations. - */ - get cdm(): DragTable { - return this._calc.tableData; + // Ensure maximumDrop and minimumAltitude are negative (like Python does) + // C++ engine expects: height < maximumDrop (triggers when bullet drops too far) + // C++ engine expects: altitude < minimumAltitude (triggers when too low) + // this.config.maximumDrop = -Math.abs(this.config.maximumDrop); + // this.config.minimumAltitude = -Math.abs(this.config.minimumAltitude); } /** * Calculates the barrel elevation required to hit a target at a specified distance. - * @param {Object} options - Parameters for the calculation. - * @param {Shot} options.shot - The shot parameters including weapon and ammo data. - * @param {number | Distance} options.targetDistance - The distance to the target, can be a number or Distance object. - * @returns {Angular} - The required barrel elevation in radians. + * + * This method automatically initializes the WASM module on first call. + * + * @param shot The shot parameters including weapon and ammo data + * @param targetDistance The distance to the target (number in default units or Distance object) + * @returns The required barrel elevation + * + * @example + * ```typescript + * const calc = new Calculator(); + * const elevation = await calc.barrelElevationForTarget(shot, 1000); + * console.log(`Elevation: ${elevation.In(Angular.Radian)} rad`); + * ``` */ - barrelElevationForTarget( + async barrelElevationForTarget( shot: Shot, - targetDistance: number | Distance, - ): Angular { - const _targetDistance = unitTypeCoerce( - targetDistance, - Distance, - preferredUnits.distance, - ); - const totalElevation = this._calc.zeroAngle(shot, _targetDistance); - return UNew.Radian( - totalElevation.In(Angular.Radian) - - shot.lookAngle.In(Angular.Radian), + targetDistance: number | Distance + ): Promise { + const _targetDistance = unitTypeCoerce(targetDistance, Distance, preferredUnits.distance); + + // Auto-initialize WASM if needed + const engine = await WasmManager.init(); + + const totalElevationRad = engine.findZeroAngle( + shot.toWasmShotProps(this.method, this.config), + _targetDistance.foot ); + + return UNew.Radian(totalElevationRad - shot.lookAngle.rad); } /** * Sets the weapon's zero elevation based on the specified zero distance. - * @param {Shot} shot - The shot parameters including weapon and ammo data. - * @param {number | Distance} zeroDistance - The distance at which the weapon should be zeroed, can be a number or Distance object. - * @returns {Angular} - The new zero elevation of the weapon in radians. + * + * This method automatically initializes the WASM module on first call. + * Modifies the shot.weapon.zeroElevation property. + * + * @param shot The shot parameters including weapon and ammo data + * @param zeroDistance The distance at which the weapon should be zeroed + * @returns The new zero elevation of the weapon + * + * @example + * ```typescript + * const calc = new Calculator(); + * const zero = await calc.setWeaponZero(shot, 100); + * console.log(`Zero elevation: ${zero.In(Angular.MOA)} MOA`); + * ``` */ - setWeaponZero(shot: Shot, zeroDistance: number | Distance): Angular { - shot.weapon.zeroElevation = this.barrelElevationForTarget( - shot, - zeroDistance, - ); + async setWeaponZero(shot: Shot, zeroDistance: number | Distance): Promise { + shot.weapon.zeroElevation = await this.barrelElevationForTarget(shot, zeroDistance); return shot.weapon.zeroElevation; } /** - * Fires a shot and calculates the hit result over a specified trajectory range. - * @param {Object} options - Parameters for the shot and trajectory calculation. - * @param {Shot} options.shot - The shot parameters including weapon and ammo data. - * @param {number | Distance} options.trajectoryRange - The total range of the trajectory, can be a number or Distance object. - * @param {number | Distance} [options.trajectoryStep=0] - The step size for trajectory calculations, can be a number or Distance object. Default is 0. - * @param {boolean} [options.extraData=false] - Flag indicating whether to include extra data in the result. Default is false. - * @returns {HitResult} - The result of the shot, including information about the hit. + * Fires a shot and calculates the complete trajectory. + * + * This method automatically initializes the WASM module on first call. + * + * @param options Parameters for the shot and trajectory calculation + * @param options.shot The shot parameters including weapon and ammo data + * @param options.trajectoryRange The total range of the trajectory + * @param options.trajectoryStep The step size for trajectory calculations (default: range/10) + * @param options.timeStep Time step for integration (default: 0) + * @param options.filterFlags Trajectory data filter flags (default: ALL) + * @param options.denseOutput Whether to generate dense output (default: false) + * @param options.raiseRangeError Should throw an error on termination (default: false) + * @returns The complete trajectory result including hit data + * + * @example + * ```typescript + * const calc = new Calculator(); + * + * // Simple trajectory + * const result = await calc.fire({ + * shot, + * trajectoryRange: 1000, + * trajectoryStep: 10 + * }); + * + * // With all options + * const result = await calc.fire({ + * shot, + * trajectoryRange: 2000, + * trajectoryStep: 25, + * timeStep: 0.01, + * filterFlags: engine._TrajFlag.BASIC, + * denseOutput: true + * }); + * + * console.log(`Impact velocity: ${result.trajectory[result.trajectory.length - 1].velocity}`); + * ``` */ - fire({ + async fire({ shot, trajectoryRange, trajectoryStep = 0.0, - extraData = false, timeStep = 0.0, + filterFlags = TrajFlag.RANGE, + denseOutput = false, + raiseRangeError = true, }: { shot: Shot; trajectoryRange: number | Distance; trajectoryStep?: number | Distance; - extraData?: boolean; timeStep?: number; - }): HitResult { - const _trajectoryRange: Distance = unitTypeCoerce( - trajectoryRange, - Distance, - preferredUnits.distance, - ); - let step = undefined; + filterFlags?: TrajFlag; + denseOutput?: boolean; + raiseRangeError?: boolean; + }): Promise { + // Convert trajectory range to Distance + const _trajectoryRange = unitTypeCoerce(trajectoryRange, Distance, preferredUnits.distance); + + // Calculate step size (default: same as range to match Python behavior) + let step: Distance; if (!trajectoryStep) { - step = UNew.Inch(_trajectoryRange.rawValue / 10.0); + step = _trajectoryRange; // Match Python: dist_step = trajectory_range } else { - step = unitTypeCoerce( - trajectoryStep, - Distance, - preferredUnits.distance, - ); + step = unitTypeCoerce(trajectoryStep, Distance, preferredUnits.distance); + } + + // Auto-initialize WASM if needed + const engine = await WasmManager.init(); + + // Build trajectory request + const request: TrajectoryRequest = { + range_limit_ft: _trajectoryRange.foot, + range_step_ft: step.foot, + time_step: timeStep, + dense_output: denseOutput, + filter_flags: filterFlags as unknown as _TrajFlag, + }; + + // Debug: log request for vertical shot + const angleInDeg = (shot.relativeAngle.rad * 180) / Math.PI; + if (Math.abs(angleInDeg - 90) < 0.1) { + // If near-vertical + console.log("[Calculator.fire] Vertical shot request:", { + angleInDeg, + range_limit_ft: request.range_limit_ft, + range_step_ft: request.range_step_ft, + filter_flags: filterFlags, + filter_flags_typeof: typeof request.filter_flags, + filter_flags_value: request.filter_flags, + }); } - const data = this._calc.trajectory( - shot, - _trajectoryRange, - step, - extraData, - timeStep, + // Calculate trajectory + const hit_out: HitOutput = engine.integrate( + shot.toWasmShotProps(this.method, this.config), + request ); - return new HitResult(shot, data, extraData); + + return HitResult.fromWasmHitOutput(shot, hit_out, raiseRangeError, filterFlags); } } - -export default Calculator; diff --git a/src/munition.ts b/src/munition.ts index 6c58e1a..445be47 100644 --- a/src/munition.ts +++ b/src/munition.ts @@ -11,6 +11,7 @@ import { preferredUnits, } from "./unit"; import { DragModel } from "./drag_model.js"; +import { _ShotPropsInput } from "./_wasm"; class Weapon { readonly sightHeight: Distance; @@ -20,30 +21,29 @@ class Weapon { /** * Initializes a new instance of the Weapon class. * @param {Object} options - Parameters for initializing the weapon. - * @param {number | Distance | null} [options.sightHeight=null] - Height of the sight above the bore axis. - * @param {number | Distance | null} [options.twist=null] - The twist rate of the barrel. - * @param {number | Angular | null} [options.zeroElevation=null] - The look angle for the zero distance. + * @param {number | Distance} [options.sightHeight=undefined] - Height of the sight above the bore axis. + * @param {number | Distance} [options.twist=undefined] - The twist rate of the barrel. + * @param {number | Angular} [options.zeroElevation=undefined] - The look angle for the zero distance. */ constructor({ - sightHeight = null, - twist = null, - zeroElevation = null, + sightHeight = undefined, + twist = undefined, + zeroElevation = undefined, }: { - sightHeight?: number | Distance | null; - twist?: number | Distance | null; - zeroElevation?: number | Angular | null; + sightHeight?: number | Distance; + twist?: number | Distance; + zeroElevation?: number | Angular; } = {}) { - this.sightHeight = unitTypeCoerce( - sightHeight ?? 0, - Distance, - preferredUnits.sight_height, - ); + this.sightHeight = unitTypeCoerce(sightHeight ?? 0, Distance, preferredUnits.sight_height); this.twist = unitTypeCoerce(twist ?? 0, Distance, preferredUnits.twist); - this.zeroElevation = unitTypeCoerce( - zeroElevation ?? 0, - Angular, - preferredUnits.angular, - ); + this.zeroElevation = unitTypeCoerce(zeroElevation ?? 0, Angular, preferredUnits.angular); + } + + toWasmWeaponInput(): Pick<_ShotPropsInput, "sight_height_ft" | "twist_inch"> { + return { + sight_height_ft: this.sightHeight.foot, + twist_inch: this.twist.inch, + }; } } @@ -59,31 +59,28 @@ class Ammo { * @param {DragModel} options.dm - Drag model instance. * @param {number | Velocity} options.mv - Velocity value. * @param {number} [options.tempModifier=0] - Temperature modifier value. Defaults to 0. - * @param {number | Temperature | null} [options.powderTemp=null] - Powder temperature value. Defaults to null. + * @param {number | Temperature} [options.powderTemp=undefined] - Powder temperature value. Defaults to undefined. * @param {boolean} [options.usePowderSensitivity=false] - Use powder sensitivity value. Defaults to false. */ constructor({ dm, mv, - powderTemp = null, + powderTemp = undefined, tempModifier = 0, usePowderSensitivity = false, }: { dm: DragModel; mv: number | Velocity; - powderTemp?: number | Temperature | null; + powderTemp?: number | Temperature; tempModifier?: number; usePowderSensitivity?: boolean; }) { - if (!dm) { - throw new Error("'dm' have to be an instance of 'DragModel'"); - } this.dm = dm; this.mv = unitTypeCoerce(mv ?? 0, Velocity, preferredUnits.velocity); this.powderTemp = unitTypeCoerce( powderTemp ?? UNew.Celsius(15), Temperature, - preferredUnits.temperature, + preferredUnits.temperature ); this.tempModifier = tempModifier ?? 0; this.usePowderSensitivity = usePowderSensitivity; @@ -97,20 +94,20 @@ class Ammo { */ calcPowderSens( otherVelocity: number | Velocity, - otherTemperature: number | Temperature, + otherTemperature: number | Temperature ): number { const v0 = this.mv.In(Velocity.MPS); const t0 = this.powderTemp.In(Temperature.Celsius); - const v1 = unitTypeCoerce( - otherVelocity, - Velocity, - preferredUnits.velocity, - ).In(Velocity.MPS); - const t1 = unitTypeCoerce( - otherTemperature, - Temperature, - preferredUnits.temperature, - ).In(Temperature.Celsius); + const v1 = unitTypeCoerce(otherVelocity, Velocity, preferredUnits.velocity).In( + Velocity.MPS + ); + const t1 = unitTypeCoerce(otherTemperature, Temperature, preferredUnits.temperature).In( + Temperature.Celsius + ); + + if (v0 <= 0 || v1 <= 0) { + throw new Error("calcPowderSens requires positive muzzle velocities"); + } const vDelta = Math.abs(v0 - v1); const tDelta = Math.abs(t0 - t1); @@ -118,7 +115,7 @@ class Ammo { if (vDelta === 0 || tDelta === 0) { throw new Error( - "Temperature modifier error, other velocity and temperature can't be same as default", + "Temperature modifier error, other velocity and temperature can't be same as default" ); } this.tempModifier = (vDelta / tDelta) * (15 / vLower); // * 100 @@ -139,15 +136,26 @@ class Ammo { return UNew.MPS(0); } const t0 = this.powderTemp.In(Temperature.Celsius); - const t1 = unitTypeCoerce( - currentTemp, - Temperature, - preferredUnits.temperature, - ).In(Temperature.Celsius); + const t1 = unitTypeCoerce(currentTemp, Temperature, preferredUnits.temperature).In( + Temperature.Celsius + ); const tDelta = t1 - t0; const muzzleVelocity = (this.tempModifier / (15 / v0)) * tDelta + v0; return UNew.MPS(muzzleVelocity); } + + toWasmAmmoInput(): Pick< + _ShotPropsInput, + "bc" | "drag_table" | "weight_grain" | "diameter_inch" | "length_inch" + > { + return { + bc: this.dm.bc, + drag_table: this.dm.dragTable, + weight_grain: this.dm.weight.grain, + diameter_inch: this.dm.diameter.inch, + length_inch: this.dm.length.inch, + }; + } } export { Weapon, Ammo }; diff --git a/src/shot.ts b/src/shot.ts new file mode 100644 index 0000000..f93529f --- /dev/null +++ b/src/shot.ts @@ -0,0 +1,299 @@ +import { _ShotPropsInput, Config, IntegrationMethod } from "./_wasm"; +import { Atmo, Coriolis, Wind } from "./conditions"; +import { Ammo, Weapon } from "./munition"; +import { Angular, preferredUnits, UNew, unitTypeCoerce } from "./unit"; + +/** + * Represents the parameters required for calculating a shot's trajectory. + * + * @example + * ```typescript + * const shot = new Shot({ + * ammo: new Ammo(...), + * atmo: Atmo.icao(), + * weapon: new Weapon(...), + * winds: [new Wind(...)], + * lookAngle: UNew.Degree(5), + * cantAngle: UNew.Degree(0), + * relativeAngle: UNew.Degree(1), + * azimuthDeg: 90, // East + * latitudeDeg: 45 // 45° North + * }); + * ``` + */ +class Shot { + /** Ammunition used for the shot */ + ammo: Ammo; + + /** Atmospheric conditions in effect during shot */ + atmo: Atmo; + + /** Weapon used for the shot */ + weapon: Weapon; + + /** + * Angle of sight line relative to horizontal (slant angle). + * + * If `lookAngle != 0` then any target in sight crosshairs will be at a different altitude: + * - Horizontal distance X to target = cos(lookAngle) * target_distance + * - Vertical distance Y to target = sin(lookAngle) * target_distance + */ + lookAngle: Angular; + + /** + * Elevation adjustment (a.k.a. "hold") added to `weapon.zeroElevation`. + */ + relativeAngle: Angular; + + /** + * Tilt of gun from vertical. + * + * If `weapon.sightHeight != 0` then this shifts any barrel elevation + * from the vertical plane into the horizontal plane (as `barrelAzimuth`) by `sin(cantAngle)`. + */ + cantAngle: Angular; + + protected _winds?: Wind[]; + protected _coriolis: { + azimuthDeg?: number; + latitudeDeg?: number; + }; + + /** + * Creates an instance of the Shot class. + * + * @param options - The parameters for initializing the shot data + * @param options.ammo - The ammunition used for the shot + * @param options.weapon - The weapon used for the shot + * @param options.atmo - Atmospheric conditions affecting the shot (defaults to ICAO standard) + * @param options.winds - List of wind conditions affecting the shot + * @param options.lookAngle - Angle of sight line relative to horizontal. + * If `lookAngle != 0` then target in crosshairs will be at different altitude: + * - Horizontal distance X = cos(lookAngle) * target_distance + * - Vertical distance Y = sin(lookAngle) * target_distance + * @param options.relativeAngle - Elevation adjustment ("hold") added to `weapon.zeroElevation` + * @param options.cantAngle - Tilt of gun from vertical. Shifts barrel elevation + * from vertical plane into horizontal plane by `sin(cantAngle)` + * @param options.coriolis - Coriolis azimuth and latitude + * @param options.coriolis.azimuthDeg - Azimuth of shooting direction in degrees [0, 360). Optional, for Coriolis effects. + * Geographic bearing: 0 = North, 90 = East, 180 = South, 270 = West + * @param options.coriolis.latitudeDeg - Latitude of shooting location in degrees [-90, 90]. Optional, for Coriolis effects + * + * @example + * ```typescript + * const shot = new Shot({ + * ammo: new Ammo(...), + * weapon: new Weapon(...), + * lookAngle: UNew.Degree(5), + * azimuthDeg: 90, + * latitudeDeg: 45 + * }); + * ``` + */ + constructor({ + weapon, + ammo, + lookAngle = undefined, + relativeAngle = undefined, + cantAngle = undefined, + atmo = undefined, + winds = undefined, + coriolis = undefined, + }: { + ammo: Ammo; + atmo?: Atmo; + weapon: Weapon; + winds?: Wind[]; + lookAngle?: number | Angular; + relativeAngle?: number | Angular; + cantAngle?: number | Angular; + coriolis?: { + azimuthDeg?: number; + latitudeDeg?: number; + }; + }) { + this.lookAngle = unitTypeCoerce(lookAngle ?? 0, Angular, preferredUnits.angular); + this.relativeAngle = unitTypeCoerce(relativeAngle ?? 0, Angular, preferredUnits.angular); + this.cantAngle = unitTypeCoerce(cantAngle ?? 0, Angular, preferredUnits.angular); + this.weapon = weapon; + this.ammo = ammo; + this.atmo = atmo ?? Atmo.icao(); + this.winds = winds; + this._coriolis = coriolis ?? {}; + } + + /** + * Azimuth of the shooting direction in degrees [0, 360). + * + * Should be *geographic* bearing where 0 = North, 90 = East, 180 = South, 270 = West. + * Difference from *magnetic* bearing is usually negligible. + * Optional, used for Coriolis effects. + */ + get azimuthDeg(): number | undefined { + return this._coriolis?.azimuthDeg; + } + + set azimuthDeg(value: number | undefined) { + if (value !== undefined && (value < 0.0 || value >= 360.0)) { + throw new Error("Azimuth must be in range [0, 360)."); + } + this._coriolis.azimuthDeg = value; + } + + /** + * Latitude of the shooting location in degrees [-90, 90]. + * + * Optional, used for Coriolis effects. + */ + get latitudeDeg(): number | undefined { + return this._coriolis.latitudeDeg; + } + + set latitudeDeg(value: number | undefined) { + if (value !== undefined && (value < -90.0 || value > 90.0)) { + throw new Error("Latitude must be in range [-90, 90]."); + } + this._coriolis.latitudeDeg = value; + } + + /** + * Sets wind conditions affecting the shot. + * Winds will be automatically sorted by `untilDistance` when retrieved. + */ + set winds(winds: Wind[] | undefined) { + this._winds = winds; + } + + /** + * Gets wind conditions sorted by `untilDistance`. + * + * @returns Array of Wind instances sorted by until_distance, or empty array if none set + */ + get winds(): Wind[] { + return (this._winds ?? []) + .slice() + .sort((a, b) => a.untilDistance.rawValue - b.untilDistance.rawValue); + } + + /** + * Gets the horizontal angle of the barrel relative to the sight line. + * + * The azimuth angle is calculated based on the cant angle and the relative angle of the + * weapon. The result is converted to radians. + * + * Calculated as: `sin(cantAngle) * (weapon.zeroElevation + relativeAngle)` + * + * @returns Angular value representing horizontal barrel angle + */ + get barrelAzimuth(): Angular { + return UNew.Radian( + Math.sin(this.cantAngle.rad) * (this.weapon.zeroElevation.rad + this.relativeAngle.rad) + ); + } + + /** + * Gets the barrel elevation in the vertical plane from the horizontal. + * + * The elevation is calculated by adding the look angle to the vertical component of + * the barrel's elevation based on the cant angle and relative angle. The result is + * converted to radians. + * + * Calculated as: `lookAngle + cos(cantAngle) * (weapon.zeroElevation + relativeAngle)` + * + * @returns Angular value representing vertical barrel elevation + */ + get barrelElevation(): Angular { + return UNew.Radian( + this.lookAngle.rad + + Math.cos(this.cantAngle.rad) * + (this.weapon.zeroElevation.rad + this.relativeAngle.rad) + ); + } + + /** + * Sets barrel elevation by adjusting `relativeAngle`. + * + * This does not change `weapon.zeroElevation`. + * Calculates required `relativeAngle` to achieve desired barrel elevation. + * + * @param value - Desired barrel elevation in vertical plane from horizontal + */ + set barrelElevation(value: Angular | number) { + this.relativeAngle = UNew.Radian( + unitTypeCoerce(value, Angular, preferredUnits.angular).rad - + this.lookAngle.rad - + Math.cos(this.cantAngle.rad) * this.weapon.zeroElevation.rad + ); + } + + /** + * Synonym for `lookAngle`. + * + * @returns Angle of sight line relative to horizontal + */ + get slantAngle(): Angular { + return this.lookAngle; + } + + set slantAngle(value: Angular | number) { + this.lookAngle = unitTypeCoerce(value, Angular, preferredUnits.angular); + } + + /** + * Converts Shot instance to WASM-compatible input format. + * + * Serializes all shot parameters into the format required by the WASM ballistic calculator. + * Includes ballistic coefficient, angles, atmospheric conditions, winds, and calculation config. + * + * @param method - Integration method to use (RK4 or EULER) + * @param config - Optional partial configuration to override defaults + * @returns WASM-compatible shot properties object + * + * @example + * ```typescript + * const wasmInput = shot.toWasmShotPropsInput(IntegrationMethod.RK4, { + * maxIterations: 50, + * minimumVelocity: 100.0 + * }); + * ``` + */ + toWasmShotProps(method: IntegrationMethod, config: Config): _ShotPropsInput { + const muzzle_velocity_fps = this.ammo.getVelocityForTemp(this.atmo.powderTemp).fps; + + return { + // Ballistic properties + ...this.ammo.toWasmAmmoInput(), + + // Velocity (adjusted for powder temperature) + muzzle_velocity_fps: muzzle_velocity_fps, + + // Angles + look_angle_rad: this.lookAngle.rad, + barrel_elevation_rad: this.barrelElevation.rad, + barrel_azimuth_rad: this.barrelAzimuth.rad, + cant_angle_rad: this.cantAngle.rad, + + // Weapon properties + ...this.weapon.toWasmWeaponInput(), + + // Environmental conditions + alt0_ft: this.atmo.altitude.foot, + atmo: this.atmo.toWasmAtmo(), + // winds: this.winds.map(wind => wind.toWasmWind()), + winds: this.winds.map((wind) => wind.toWasmWind()), + + // Coriolis + coriolis: new Coriolis({ + latitudeDeg: this.latitudeDeg, + azimuthDeg: this.azimuthDeg, + muzzleVelocityFps: muzzle_velocity_fps, + }).toWasmCoriolis(), + + // Calculation options + method: method, + config: config, + }; + } +} + +export { Shot }; diff --git a/src/test.html b/src/test.html new file mode 100644 index 0000000..32bab86 --- /dev/null +++ b/src/test.html @@ -0,0 +1,227 @@ + + + + + + + Ballistics Calculator Test + + + + +

🎯 Ballistics Calculator Test

+

Check browser console (F12) for detailed output

+ + + + + +
Waiting for test...
+ + + + + diff --git a/src/trajectory_data.ts b/src/trajectory_data.ts index 836efeb..3ad7009 100644 --- a/src/trajectory_data.ts +++ b/src/trajectory_data.ts @@ -4,26 +4,25 @@ import { UnitProps, Unit, preferredUnits, - unitTypeCoerce, Angular, Distance, Velocity, Energy, Weight, - AbstractUnit, + Dimension, + UNew, } from "./unit"; -import { Shot } from "./conditions"; - -enum TrajFlag { - NONE = 0, - ZERO_UP = 1 << 0, - ZERO_DOWN = 1 << 1, - MACH = 1 << 2, - RANGE = 1 << 3, - ZERO = ZERO_UP | ZERO_DOWN, - APEX = 1 << 4, // Corrected APEX bit value - ALL = ZERO | MACH | RANGE | APEX, // Corrected ALL value -} +import { Shot } from "./shot"; +import { + _TrajectoryData, + _TrajectoryDataInterpKey, + _InterpMethod, + HitOutput, + TrajFlag, + TerminationReason, + WasmManager, +} from "./_wasm"; +import { RangeError } from "./exceptions"; const trajFlagNames: Record = { [TrajFlag.NONE]: "NONE", @@ -34,6 +33,7 @@ const trajFlagNames: Record = { [TrajFlag.RANGE]: "RANGE", [TrajFlag.APEX]: "APEX", [TrajFlag.ALL]: "ALL", + [TrajFlag.MRT]: "MRT", }; const trajFlagName = (value: TrajFlag) => { @@ -60,9 +60,7 @@ const trajFlagName = (value: TrajFlag) => { (value & TrajFlag.ZERO_DOWN) === TrajFlag.ZERO_DOWN ) { // If ZERO_UP and ZERO_DOWN are both in parts, replace them with "ZERO" - parts = parts.filter( - (part) => part !== "ZERO_UP" && part !== "ZERO_DOWN", - ); + parts = parts.filter((part) => part !== "ZERO_UP" && part !== "ZERO_DOWN"); // Only add "ZERO" if it's not already implicitly handled by direct lookup // and if it makes sense as a combined flag. if (!parts.includes("ZERO")) { @@ -82,22 +80,22 @@ class TrajectoryData { * This class is used solely as a return value from trajectory calculations. * * @class - * @param {number} time - The time elapsed in the trajectory calculation. - * @param {Distance} distance - The distance traveled. + * @param {number} time - Flight time in seconds + * @param {Distance} distance - Down-range (x-axis) coordinate of this point * @param {Velocity} velocity - The velocity at the given point. - * @param {number} mach - The Mach number at the given point. - * @param {Distance} height - The height above the reference point. - * @param {Distance} targetDrop - The drop from the target elevation. - * @param {Angular} dropAdjustment - Adjustment in angle due to drop. - * @param {Distance} windage - The amount of windage correction. - * @param {Angular} windageAdjustment - Adjustment in angle due to windage. - * @param {Distance} lookDistance - The distance to the target. - * @param {Angular} angle - The angle of the trajectory. - * @param {number} densityFactor - Factor representing air density effects. - * @param {number} drag - The drag experienced by the projectile. - * @param {Energy} energy - The energy of the projectile. - * @param {Weight} ogw - The optimal gun weight. - * @param {TrajFlag} flag - Flags representing various trajectory characteristics. + * @param {number} mach - Velocity in Mach terms + * @param {Distance} height - Vertical (y-axis) coordinate of this point + * @param {Distance} slantHeight - Distance orthogonal to sight-line + * @param {Angular} dropAngle - Slant_height in angular terms + * @param {Distance} windage - Windage (z-axis) coordinate of this point + * @param {Angular} windageAngle - Windage in angular terms + * @param {Distance} slantDistance - Distance along sight line that is closest to this point + * @param {Angular} angle - Angle of velocity vector relative to x-axis + * @param {number} densityRatio - Ratio of air density here to standard density + * @param {number} drag - Standard Drag Factor at this point + * @param {Energy} energy - Energy of bullet at this point + * @param {Weight} ogw - Optimal game weight, given .energy + * @param {TrajFlag} flag - Row type */ constructor( readonly time: number, @@ -105,18 +103,18 @@ class TrajectoryData { readonly velocity: Velocity, readonly mach: number, readonly height: Distance, - readonly targetDrop: Distance, - readonly dropAdjustment: Angular, + readonly slantHeight: Distance, + readonly dropAngle: Angular, readonly windage: Distance, - readonly windageAdjustment: Angular, - readonly lookDistance: Distance, + readonly windageAngle: Angular, + readonly slantDistance: Distance, readonly angle: Angular, - readonly densityFactor: number, + readonly densityRatio: number, readonly drag: number, readonly energy: Energy, readonly ogw: Weight, - readonly flag: TrajFlag, - ) { } // Properties are automatically assigned due to 'readonly' and constructor parameters + readonly flag: TrajFlag + ) {} // Properties are automatically assigned due to 'readonly' and constructor parameters /** * Returns an array of numerical values representing the trajectory data in default units. @@ -131,13 +129,13 @@ class TrajectoryData { this.velocity.In(preferredUnits.velocity), this.mach, this.height.In(preferredUnits.drop), // Changed to preferredUnits.drop as per python - this.targetDrop.In(preferredUnits.drop), - this.dropAdjustment.In(preferredUnits.adjustment), + this.slantHeight.In(preferredUnits.drop), + this.dropAngle.In(preferredUnits.adjustment), this.windage.In(preferredUnits.drop), - this.windageAdjustment.In(preferredUnits.adjustment), - this.lookDistance.In(preferredUnits.distance), + this.windageAngle.In(preferredUnits.adjustment), + this.slantDistance.In(preferredUnits.distance), this.angle.In(preferredUnits.angular), - this.densityFactor, + this.densityRatio, this.drag, this.energy.In(preferredUnits.energy), this.ogw.In(preferredUnits.ogw), @@ -152,11 +150,14 @@ class TrajectoryData { */ formatted(): string[] { /** simple formatter - * @param {AbstractUnit} value + * @param {Dimension} value * @param {Unit} unit * @return {string} time */ - function _fmt(value: AbstractUnit, unit: Unit): string { + function _fmt( + value: Dimension, + unit: AllowedUnitT + ): string { return `${value.In(unit).toFixed(UnitProps[unit].accuracy)} ${UnitProps[unit].symbol}`; } @@ -166,79 +167,103 @@ class TrajectoryData { _fmt(this.velocity, preferredUnits.velocity), `${this.mach.toFixed(2)} mach`, _fmt(this.height, preferredUnits.drop), // Changed to preferredUnits.drop as per python - _fmt(this.targetDrop, preferredUnits.drop), - _fmt(this.dropAdjustment, preferredUnits.adjustment), + _fmt(this.slantHeight, preferredUnits.drop), + _fmt(this.dropAngle, preferredUnits.adjustment), _fmt(this.windage, preferredUnits.drop), - _fmt(this.windageAdjustment, preferredUnits.adjustment), - _fmt(this.lookDistance, preferredUnits.distance), + _fmt(this.windageAngle, preferredUnits.adjustment), + _fmt(this.slantDistance, preferredUnits.distance), _fmt(this.angle, preferredUnits.angular), - `${this.densityFactor.toFixed(3)}`, + `${this.densityRatio.toFixed(3)}`, `${this.drag.toFixed(3)}`, _fmt(this.energy, preferredUnits.energy), _fmt(this.ogw, preferredUnits.ogw), `${trajFlagName(this.flag)}`, ]; } -} -class DangerSpace { /** - * Stores the danger space data for a specified distance. - * ! DATACLASS, USES AS RETURNED VALUE ONLY + * Converts TrajectoryData instance to WASM-compatible format. * - * @param {TrajectoryData} atRange - The trajectory data at the specified range. - * @param {Distance} targetHeight - The height of the target, or null if not applicable. - * @param {TrajectoryData} begin - The starting trajectory data for the danger space. - * @param {TrajectoryData} end - The ending trajectory data for the danger space. - * @param {Angular} lookAngle - The look angle for the danger space, or null if not applicable. - */ - constructor( - readonly atRange: TrajectoryData, - readonly targetHeight: Distance, - readonly begin: TrajectoryData, - readonly end: TrajectoryData, - readonly lookAngle: Angular, - ) { } - - /** - * Returns a string representation of the DangerSpace object. - * @returns {string} - A string summarizing the DangerSpace data. + * This method serializes all trajectory data fields into the raw format + * expected by WASM functions like interpolateTrajectoryData. + * + * @returns WASM-compatible trajectory data object */ - toString(): string { - let str = `Danger space at ${this.atRange.distance.to(preferredUnits.distance)} ` + - `for ${this.targetHeight.to(preferredUnits.drop)} tall target`; - - if (this.lookAngle.rawValue !== 0) { - str += ` at ${this.lookAngle.to(Angular.Degree)} look-angle`; - } + toWasmTrajectoryData(): _TrajectoryData { + return { + time: this.time, + distance_ft: this.distance.foot, + velocity_fps: this.velocity.fps, + mach: this.mach, + height_ft: this.height.foot, + slant_height_ft: this.slantHeight.foot, + drop_angle_rad: this.dropAngle.rad, + windage_ft: this.windage.foot, + windage_angle_rad: this.windageAngle.rad, + slant_distance_ft: this.slantDistance.foot, + angle_rad: this.angle.rad, + density_ratio: this.densityRatio, + drag: this.drag, + energy_ft_lb: this.energy.footPound, + ogw_lb: this.ogw.pound, + flag: this.flag, + }; + } - str += ` ranges from ${this.begin.distance.to(preferredUnits.distance)} ` + - `to ${this.end.distance.to(preferredUnits.distance)}`; - return str; + static fromWasmTrajectoryData(data: _TrajectoryData) { + return new TrajectoryData( + data.time, + UNew.Foot(data.distance_ft), + UNew.FPS(data.velocity_fps), + data.mach, + UNew.Foot(data.height_ft), + UNew.Foot(data.slant_height_ft), + UNew.Radian(data.drop_angle_rad), + UNew.Foot(data.windage_ft), + UNew.Radian(data.windage_angle_rad), + UNew.Foot(data.slant_distance_ft), + UNew.Radian(data.angle_rad), + data.density_ratio, + data.drag, + UNew.FootPound(data.energy_ft_lb), + UNew.Pound(data.ogw_lb), + data.flag + ); } } class HitResult { /** - * Results of the shot - * ! DATACLASS, USES AS RETURNED VALUE ONLY - * @param {Shot} shot - * @param {TrajectoryData[]} _trajectory - * @param {boolean} _extra + * Computed trajectory data of the shot. + * + * @param shot - The parameters of the shot calculation + * @param trajectory - Computed TrajectoryData points + * @param error - RangeError if any (optional) + * @param filterFlags - Flags that were requested in the trajectory calculation */ readonly shot: Shot; readonly trajectory: TrajectoryData[]; - readonly extra: boolean; + error?: Error; + readonly filterFlags: TrajFlag; constructor( shot: Shot, trajectory: TrajectoryData[], - extra: boolean = false, + filterFlags: TrajFlag = TrajFlag.NONE, + error?: Error ) { this.shot = shot; this.trajectory = trajectory; - this.extra = extra; + this.filterFlags = filterFlags; + this.error = error; + } + + /** + * Get Shot properties (alias for shot for Python compatibility) + */ + get props(): Shot { + return this.shot; } /** @@ -258,25 +283,48 @@ class HitResult { return this.trajectory[index]; } - protected _checkExtra(): void { - if (!this.extra) { - // Using a custom message similar to Python's __repr__ + get length(): number { + return this.trajectory.length; + } + + /** + * Check if the specified flag was requested in the trajectory calculation. + * @param flag - The flag to check + * @throws Error if the flag was not requested + */ + protected _checkFlag(flag: TrajFlag): void { + // Check if the flag was requested in filter_flags + const wasRequested = (this.filterFlags & flag) !== 0; + if (!wasRequested) { + const flagName = trajFlagName(flag); throw new Error( - `${Object.getPrototypeOf(this).constructor.name} has no extra data. Use Calculator.fire(..., extra_data=true)`, + `${flagName} was not requested in trajectory. Use Calculator.fire(..., flags=TrajFlag.${flagName}) to include it.` ); } } - get length(): number { - return this.trajectory.length; + /** + * Get first TrajectoryData row with the specified flag. + * @param flag - The flag to search for + * @returns First TrajectoryData row with the specified flag, or undefined if not found + * @throws Error if the flag was not requested + */ + flag(flag: TrajFlag): TrajectoryData | undefined { + this._checkFlag(flag); + return this.trajectory.find((row) => row.flag & flag); } + /** + * Get all zero crossing points. + * @returns Array of TrajectoryData at zero crossings + * @throws Error if zero crossing points are not found + */ zeros(): TrajectoryData[] { - this._checkExtra(); + this._checkFlag(TrajFlag.ZERO); const data = this.trajectory.filter((row) => row.flag & TrajFlag.ZERO); if (data.length < 1) { - throw new Error("Can't find zero crossing points"); // Equivalent to Python's ArithmeticError, here using generic Error + throw new Error("Can't find zero crossing points"); } return data; @@ -291,7 +339,7 @@ class HitResult { // Adding epsilon to avoid floating-point issues, similar to Python const epsilon = 1e-8; return this.trajectory.findIndex( - (item) => item.distance.rawValue >= distance.rawValue - epsilon, + (item) => item.distance.rawValue >= distance.rawValue - epsilon ); } @@ -299,111 +347,204 @@ class HitResult { const index = this.indexAtDistance(d); if (index < 0) { throw new Error( - `Calculated trajectory doesn't reach requested distance ${d.rawValue}`, // Changed to d.rawValue for better output + `Calculated trajectory doesn't reach requested distance ${d.rawValue}` // Changed to d.rawValue for better output ); } return this.trajectory[index]; } /** - * Calculates the danger space for the specified range and target height. - * @param {number | Distance} atRange - The distance at which to calculate the danger space. - * @param {number | Distance} targetHeight - The height of the target. - * @param {number | Angular | null} lookAngle - The look angle for the calculation. - * @returns {DangerSpace} - The computed DangerSpace object. + * Get TrajectoryData where the specified attribute equals the target value. + * Interpolates to create a new TrajectoryData point if necessary. + * + * @param keyAttribute - The TrajectoryDataInterpKey to interpolate on + * @param value - The target value for the key attribute + * @param epsilon - Allowed difference to match existing TrajectoryData without interpolating (default: 1e-9) + * @param startFromTime - Time to center the search from (default: 0.0) + * @returns TrajectoryData where keyAttribute equals value + * @throws Error if trajectory doesn't reach the requested value + * @throws Error if interpolation requires at least 3 points */ - public dangerSpace( - atRange: number | Distance, - targetHeight: number | Distance, - lookAngle: number | Angular | null = null, - ): DangerSpace { - this._checkExtra(); - - const _atRange: Distance = unitTypeCoerce( - atRange, - Distance, - preferredUnits.distance, - ); + async getAt( + keyAttribute: _TrajectoryDataInterpKey, + value: number, + epsilon: number = 1e-9, + startFromTime: number = 0.0 + ): Promise { + const traj = this.trajectory; + const n = traj.length; + + // Helper to get raw value of the key attribute from TrajectoryData + const getKeyVal = (td: TrajectoryData): number => { + // Map _TrajectoryDataInterpKey to TrajectoryData property + const keyIndex = typeof keyAttribute === "object" ? keyAttribute : keyAttribute; + switch (keyIndex) { + case 0: + return td.time; + case 1: + return td.distance.rawValue; + case 2: + return td.velocity.rawValue; + case 3: + return td.mach; + case 4: + return td.height.rawValue; + case 5: + return td.slantHeight.rawValue; + case 6: + return td.dropAngle.rawValue; + case 7: + return td.windage.rawValue; + case 8: + return td.windageAngle.rawValue; + case 9: + return td.slantDistance.rawValue; + case 10: + return td.angle.rawValue; + case 11: + return td.densityRatio; + case 12: + return td.drag; + case 13: + return td.energy.rawValue; + case 14: + return td.ogw.rawValue; + default: + throw new Error(`Invalid interpolation key: ${keyIndex}`); + } + }; - const _targetHeight: Distance = unitTypeCoerce( - targetHeight, - Distance, - preferredUnits.distance, - ); - const _targetHeightHalf: number = _targetHeight.rawValue / 2.0; + // Check if we have enough points for interpolation + if (n < 3) { + if (Math.abs(getKeyVal(traj[0]) - value) < epsilon) { + return traj[0]; + } + if (n > 1 && Math.abs(getKeyVal(traj[1]) - value) < epsilon) { + return traj[1]; + } + throw new Error("Interpolation requires at least 3 TrajectoryData points."); + } - const _lookAngle: Angular = - lookAngle === null || lookAngle === undefined - ? this.shot.lookAngle - : unitTypeCoerce(lookAngle, Angular, preferredUnits.angular); + // Find starting index based on startFromTime + let startIdx = 0; + if (startFromTime > 0) { + startIdx = traj.findIndex((td) => td.time >= startFromTime); + if (startIdx < 0) startIdx = 0; + } - // Get index of first trajectory point with distance >= at_range - const index = this.indexAtDistance(_atRange); - if (index < 0) { - throw new Error( - `Calculated trajectory doesn't reach requested distance ${_atRange.rawValue}`, - ); + const currVal = getKeyVal(traj[startIdx]); + if (Math.abs(currVal - value) < epsilon) { + return traj[startIdx]; } - const findBeginDanger = (rowNum: number): TrajectoryData => { - /** - * Beginning of danger space is last .distance' < .distance where - * (.drop' - target_center) >= target_height/2 - * @param {number} rowNum - Index of the trajectory point for which we are calculating danger space - * @return {TrajectoryData} - Distance marking the beginning of danger space - */ - const centerRow = this.trajectory[rowNum]; - - // Iterate in reverse from rowNum - 1 down to 0, similar to Python's reversed(self.trajectory[:row_num]) - for (let i = rowNum - 1; i >= 0; i--) { - const primeRow = this.trajectory[i]; - if ( - (primeRow.targetDrop.rawValue - centerRow.targetDrop.rawValue) >= - _targetHeightHalf - ) { - return primeRow; - } + // Determine search direction + let searchForward = true; + if (startIdx === n - 1) { + searchForward = false; + } else if (startIdx > 0 && startIdx < n - 1) { + const nextVal = getKeyVal(traj[startIdx + 1]); + if ((nextVal > currVal && value > currVal) || (nextVal < currVal && value < currVal)) { + searchForward = true; + } else { + searchForward = false; } - return this.trajectory[0]; - }; + } - const findEndDanger = (rowNum: number): TrajectoryData => { - /** - * End of danger space is first .distance' > .distance where - * (target_center - .drop') >= target_height/2 - * @param {number} rowNum - Index of the trajectory point for which we are calculating danger space - * @return {TrajectoryData} - Distance marking the end of danger space - */ - const centerRow = this.trajectory[rowNum]; - - // Iterate forwards from rowNum + 1 up to the end, similar to Python's self.trajectory[row_num + 1:] - for (let i = rowNum + 1; i < this.trajectory.length; i++) { - const primeRow = this.trajectory[i]; - if ( - (centerRow.targetDrop.rawValue - primeRow.targetDrop.rawValue) >= - _targetHeightHalf - ) { - return primeRow; + // Search for target value + let targetIdx = -1; + if (searchForward) { + for (let i = startIdx; i < n - 1; i++) { + const curr = getKeyVal(traj[i]); + const next = getKeyVal(traj[i + 1]); + if ((curr < value && value <= next) || (next <= value && value < curr)) { + targetIdx = i + 1; + break; } } - return this.trajectory[this.trajectory.length - 1]; - }; + } + if (!searchForward || targetIdx === -1) { + for (let i = startIdx; i > 0; i--) { + const curr = getKeyVal(traj[i]); + const prev = getKeyVal(traj[i - 1]); + if ((prev <= value && value < curr) || (curr < value && value <= prev)) { + targetIdx = i; + break; + } + } + } + + if (targetIdx === -1) { + throw new Error( + `Trajectory does not reach the requested value ${value} for the specified key` + ); + } + + // Check for exact match + if (Math.abs(getKeyVal(traj[targetIdx]) - value) < epsilon) { + return traj[targetIdx]; + } + + // Step forward from first point if needed + if (targetIdx === 0) { + targetIdx = 1; + } + + // Choose three bracketing points (p0, p1, p2) + let p0: TrajectoryData, p1: TrajectoryData, p2: TrajectoryData; + if (targetIdx >= n - 1) { + p0 = traj[n - 3]; + p1 = traj[n - 2]; + p2 = traj[n - 1]; + } else { + p0 = traj[targetIdx - 1]; + p1 = traj[targetIdx]; + p2 = traj[targetIdx + 1]; + } - return new DangerSpace( - this.trajectory[index], - _targetHeight, - findBeginDanger(index), - findEndDanger(index), - _lookAngle, + // Use WASM interpolation + const bclibc = await WasmManager.init(); + const interpolated = bclibc.interpolateTrajectoryData( + keyAttribute, + value, + p0.toWasmTrajectoryData(), + p1.toWasmTrajectoryData(), + p2.toWasmTrajectoryData(), + TrajFlag.NONE, + bclibc._InterpMethod.PCHIP ); + + return TrajectoryData.fromWasmTrajectoryData(interpolated); + } + + static fromWasmHitOutput( + shot: Shot, + hit: HitOutput, + raiseRangeError: boolean = true, + filterFlags: TrajFlag = TrajFlag.NONE + ) { + const trajectory = (hit.trajectory as _TrajectoryData[]).map((item) => + TrajectoryData.fromWasmTrajectoryData(item) + ); + + // Check termination reason and create error if needed + let error: Error | undefined = undefined; + const reasonValue = hit.reason; + + if (reasonValue === TerminationReason.MINIMUM_VELOCITY_REACHED) { + error = new RangeError(RangeError.MinimumVelocityReached, trajectory); + } else if (reasonValue === TerminationReason.MAXIMUM_DROP_REACHED) { + error = new RangeError(RangeError.MaximumDropReached, trajectory); + } else if (reasonValue === TerminationReason.MINIMUM_ALTITUDE_REACHED) { + error = new RangeError(RangeError.MinimumAltitudeReached, trajectory); + } + + // If raiseRangeError is true and there's an error, throw it + if (raiseRangeError && error) { + throw error; + } + + return new HitResult(shot, trajectory, filterFlags, error); } } -export { - TrajectoryData, - TrajFlag, - trajFlagName, - trajFlagNames, - DangerSpace, - HitResult, -}; +export { TrajectoryData, trajFlagName, trajFlagNames, HitResult }; diff --git a/src/unit.ts b/src/unit.ts index 3f20ee6..c9d3ce4 100644 --- a/src/unit.ts +++ b/src/unit.ts @@ -45,7 +45,44 @@ enum Unit { Newton = 75, } -class AbstractUnit { +type AngularUnit = + | Unit.Radian + | Unit.Degree + | Unit.MOA + | Unit.MIL + | Unit.MRad + | Unit.Thousand + | Unit.InchesPer100Yd + | Unit.CmPer100M + | Unit.OClock; +type DistanceUnit = + | Unit.Inch + | Unit.Foot + | Unit.Yard + | Unit.Mile + | Unit.NauticalMile + | Unit.Millimeter + | Unit.Centimeter + | Unit.Meter + | Unit.Kilometer + | Unit.Line; +type VelocityUnit = Unit.MPS | Unit.KMH | Unit.FPS | Unit.MPH | Unit.KT; +type WeightUnit = Unit.Grain | Unit.Ounce | Unit.Gram | Unit.Pound | Unit.Kilogram | Unit.Newton; +type TemperatureUnit = Unit.Fahrenheit | Unit.Celsius | Unit.Kelvin | Unit.Rankin; +type EnergyUnit = Unit.FootPound | Unit.Joule; +type PressureUnit = Unit.MmHg | Unit.InHg | Unit.Bar | Unit.hPa | Unit.PSI; + +// interface Measurable { +// rawValue: number; +// unitValue: number; +// units: Unit; +// constructor: (value: number, units: Unit) => Measurable; +// toString: () => string; +// to: (units: Unit) => Measurable; +// In: (units: Unit) => number; +// } + +class Dimension { /** * Abstract class for unit of measure instance definition. * Stores defined unit and value, applies conversions to other units. @@ -54,11 +91,11 @@ class AbstractUnit { * @param {Unit} units - Unit as Unit enum. */ - ["constructor"]: typeof AbstractUnit; + ["constructor"]: typeof Dimension; _value: number; - _definedUnits: Unit; + _definedUnits: AllowedUnitT; - constructor(value: number, units: Unit) { + constructor(value: number, units: AllowedUnitT) { // this["constructor"] = this.constructor; this._value = this.toRaw(value, units); this._definedUnits = units; @@ -85,7 +122,7 @@ class AbstractUnit { * Validates the units. * * @param {number} value - Value of the unit. - * @param {Unit} units - Unit enum type. + * @param {AllowedUnitT} units - Unit enum type. * @return {number} Value in specified units. * @throws {TypeError} When the provided units are not of the expected type. * @throws {Error} When the provided units are not supported. @@ -97,9 +134,7 @@ class AbstractUnit { } if (!Object.values(this).includes(units)) { - throw new Error( - `${this.constructor.name}: unit ${units} is not supported`, - ); + throw new Error(`${this.constructor.name}: unit ${units} is not supported`); } return 0; @@ -109,10 +144,10 @@ class AbstractUnit { * Converts value with specified units to raw value. * * @param {number} value - Value of the unit. - * @param {Unit} units - Unit enum type. + * @param {AllowedUnitT} units - Unit enum type. * @return {number} Value in specified units. */ - protected toRaw(value: number, units: Unit): number { + protected toRaw(value: number, units: AllowedUnitT): number { return this._unit_support_error(value, units); } @@ -120,20 +155,22 @@ class AbstractUnit { * Converts raw value to specified units. * * @param {number} value - Raw value of the unit. - * @param {Unit} units - Unit enum type. + * @param {AllowedUnitT} units - Unit enum type. * @return {number} Value in specified units. */ - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: AllowedUnitT): number { return this._unit_support_error(value, units); } /** * Returns a new unit instance in specified units. * - * @param {Unit} units - Unit enum type. - * @return {AbstractUnit} New unit instance in specified units. + * @param {AllowedUnitT} units - Unit enum type. + * @return {Dimension} New unit instance in specified units. */ - to(units: Unit): AbstractUnit { + to(units: AllowedUnitT): Dimension; + to(units: Unit): Dimension; // Додайте перевантаження + to(units: Unit): Dimension { const value: number = this.In(units); return new this.constructor(value, units); } @@ -141,19 +178,21 @@ class AbstractUnit { /** * Returns value in specified units. * - * @param {Unit} units - Unit enum type. + * @param {AllowedUnitT} units - Unit enum type. * @return {number} Value in specified units. */ + In(units: AllowedUnitT): number; + In(units: Unit): number; // Додайте перевантаження In(units: Unit): number { - return this.fromRaw(this._value, units); + return this.fromRaw(this._value, units as AllowedUnitT); } /** * Returns defined units. * - * @return {Unit} Defined units. + * @return {AllowedUnitT} Defined units. */ - get units(): Unit { + get units(): AllowedUnitT { return this._definedUnits; } @@ -170,23 +209,27 @@ class AbstractUnit { /** * Angular unit */ -class Angular extends AbstractUnit { +class Angular extends Dimension { // Angular unit constants - static Radian = Unit.Radian; - static Degree = Unit.Degree; - static MOA = Unit.MOA; - static MIL = Unit.MIL; - static MRad = Unit.MRad; - static Thousand = Unit.Thousand; - static InchesPer100Yd = Unit.InchesPer100Yd; - static CmPer100M = Unit.CmPer100M; - static OClock = Unit.OClock; - - constructor(value: number, units: Unit) { + static readonly Radian: AngularUnit = Unit.Radian; + static readonly Degree: AngularUnit = Unit.Degree; + static readonly MOA: AngularUnit = Unit.MOA; + static readonly MIL: AngularUnit = Unit.MIL; + static readonly MRad: AngularUnit = Unit.MRad; + static readonly Thousand: AngularUnit = Unit.Thousand; + static readonly InchesPer100Yd: AngularUnit = Unit.InchesPer100Yd; + static readonly CmPer100M: AngularUnit = Unit.CmPer100M; + static readonly OClock: AngularUnit = Unit.OClock; + + constructor(value: number, units: AngularUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get rad(): number { + return this.In(Unit.Radian); + } + + protected toRaw(value: number, units: AngularUnit): number { let result = 0; switch (units) { @@ -227,7 +270,7 @@ class Angular extends AbstractUnit { return result; } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: AngularUnit): number { switch (units) { case Unit.Radian: return value; @@ -256,24 +299,32 @@ class Angular extends AbstractUnit { /** * Distance unit */ -class Distance extends AbstractUnit { +class Distance extends Dimension { // Distance unit constants - static Inch = Unit.Inch; - static Foot = Unit.Foot; - static Yard = Unit.Yard; - static Mile = Unit.Mile; - static NauticalMile = Unit.NauticalMile; - static Line = Unit.Line; - static Millimeter = Unit.Millimeter; - static Centimeter = Unit.Centimeter; - static Meter = Unit.Meter; - static Kilometer = Unit.Kilometer; - - constructor(value: number, units: Unit) { + static readonly Inch: DistanceUnit = Unit.Inch; + static readonly Foot: DistanceUnit = Unit.Foot; + static readonly Yard: DistanceUnit = Unit.Yard; + static readonly Mile: DistanceUnit = Unit.Mile; + static readonly NauticalMile: DistanceUnit = Unit.NauticalMile; + static readonly Line: DistanceUnit = Unit.Line; + static readonly Millimeter: DistanceUnit = Unit.Millimeter; + static readonly Centimeter: DistanceUnit = Unit.Centimeter; + static readonly Meter: DistanceUnit = Unit.Meter; + static readonly Kilometer: DistanceUnit = Unit.Kilometer; + + constructor(value: number, units: DistanceUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get foot(): number { + return this.In(Unit.Foot); + } + + get inch(): number { + return this.In(Unit.Inch); + } + + protected toRaw(value: number, units: DistanceUnit): number { switch (units) { case Unit.Inch: return value; @@ -300,7 +351,7 @@ class Distance extends AbstractUnit { } } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: DistanceUnit): number { switch (units) { case Unit.Inch: return value; @@ -331,19 +382,27 @@ class Distance extends AbstractUnit { /** * Velocity unit */ -class Velocity extends AbstractUnit { +class Velocity extends Dimension { // Velocity unit constants - static MPS = Unit.MPS; - static KMH = Unit.KMH; - static FPS = Unit.FPS; - static MPH = Unit.MPH; - static KT = Unit.KT; + static readonly MPS: VelocityUnit = Unit.MPS; + static readonly KMH: VelocityUnit = Unit.KMH; + static readonly FPS: VelocityUnit = Unit.FPS; + static readonly MPH: VelocityUnit = Unit.MPH; + static readonly KT: VelocityUnit = Unit.KT; - constructor(value: number, units: Unit) { + constructor(value: number, units: VelocityUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get fps(): number { + return this.In(Unit.FPS); + } + + get mps(): number { + return this.In(Unit.MPS); + } + + protected toRaw(value: number, units: VelocityUnit): number { switch (units) { case Unit.MPS: // Meters Per Second return value; @@ -360,7 +419,7 @@ class Velocity extends AbstractUnit { } } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: VelocityUnit): number { switch (units) { case Unit.MPS: // Meters Per Second return value; @@ -381,20 +440,28 @@ class Velocity extends AbstractUnit { /** * Weight unit */ -class Weight extends AbstractUnit { +class Weight extends Dimension { // Weight unit constants - static Grain = Unit.Grain; - static Ounce = Unit.Ounce; - static Gram = Unit.Gram; - static Pound = Unit.Pound; - static Kilogram = Unit.Kilogram; - static Newton = Unit.Newton; - - constructor(value: number, units: Unit) { + static readonly Grain: WeightUnit = Unit.Grain; + static readonly Ounce: WeightUnit = Unit.Ounce; + static readonly Gram: WeightUnit = Unit.Gram; + static readonly Pound: WeightUnit = Unit.Pound; + static readonly Kilogram: WeightUnit = Unit.Kilogram; + static readonly Newton: WeightUnit = Unit.Newton; + + constructor(value: number, units: WeightUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get grain(): number { + return this.In(Unit.Grain); + } + + get pound(): number { + return this.In(Unit.Pound); + } + + protected toRaw(value: number, units: WeightUnit): number { switch (units) { case Unit.Grain: return value; @@ -413,7 +480,7 @@ class Weight extends AbstractUnit { } } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: WeightUnit): number { switch (units) { case Unit.Grain: return value; @@ -436,19 +503,19 @@ class Weight extends AbstractUnit { /** * Pressure unit */ -class Pressure extends AbstractUnit { +class Pressure extends Dimension { // Pressure unit constants - static MmHg = Unit.MmHg; - static InHg = Unit.InHg; - static Bar = Unit.Bar; - static hPa = Unit.hPa; - static PSI = Unit.PSI; + static readonly MmHg: PressureUnit = Unit.MmHg; + static readonly InHg: PressureUnit = Unit.InHg; + static readonly Bar: PressureUnit = Unit.Bar; + static readonly hPa: PressureUnit = Unit.hPa; + static readonly PSI: PressureUnit = Unit.PSI; - constructor(value: number, units: Unit) { + constructor(value: number, units: PressureUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + protected toRaw(value: number, units: PressureUnit): number { switch (units) { case Unit.MmHg: // Millimeters of Mercury (base unit) return value; @@ -465,7 +532,7 @@ class Pressure extends AbstractUnit { } } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: PressureUnit): number { switch (units) { case Unit.MmHg: // Millimeters of Mercury (base unit) return value; @@ -486,18 +553,22 @@ class Pressure extends AbstractUnit { /** * Temperature unit */ -class Temperature extends AbstractUnit { +class Temperature extends Dimension { // Temperature unit constants - static Fahrenheit = Unit.Fahrenheit; - static Celsius = Unit.Celsius; - static Kelvin = Unit.Kelvin; - static Rankin = Unit.Rankin; + static readonly Fahrenheit: TemperatureUnit = Unit.Fahrenheit; + static readonly Celsius: TemperatureUnit = Unit.Celsius; + static readonly Kelvin: TemperatureUnit = Unit.Kelvin; + static readonly Rankin: TemperatureUnit = Unit.Rankin; - constructor(value: number, units: Unit) { + constructor(value: number, units: TemperatureUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get celsius(): number { + return this.In(Unit.Celsius); + } + + protected toRaw(value: number, units: TemperatureUnit): number { switch (units) { case Unit.Fahrenheit: return value; @@ -512,7 +583,7 @@ class Temperature extends AbstractUnit { } } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: TemperatureUnit): number { switch (units) { case Unit.Fahrenheit: return value; @@ -531,16 +602,20 @@ class Temperature extends AbstractUnit { /** * Energy unit */ -class Energy extends AbstractUnit { +class Energy extends Dimension { // Energy unit constants - static FootPound = Unit.FootPound; - static Joule = Unit.Joule; + static readonly FootPound: EnergyUnit = Unit.FootPound; + static readonly Joule: EnergyUnit = Unit.Joule; - constructor(value: number, units: Unit) { + constructor(value: number, units: EnergyUnit) { super(value, units); } - protected toRaw(value: number, units: Unit): number { + get footPound(): number { + return this.In(Unit.FootPound); + } + + protected toRaw(value: number, units: EnergyUnit): number { if (units === Unit.FootPound) { return value; } @@ -550,7 +625,7 @@ class Energy extends AbstractUnit { return super.toRaw(value, units); } - protected fromRaw(value: number, units: Unit): number { + protected fromRaw(value: number, units: EnergyUnit): number { if (units === Unit.FootPound) { return value; } @@ -675,16 +750,14 @@ const UNew = { [Unit.MIL]: (value: number) => new Angular(value, Unit.MIL), [Unit.MRad]: (value: number) => new Angular(value, Unit.MRad), [Unit.Thousand]: (value: number) => new Angular(value, Unit.Thousand), - [Unit.InchesPer100Yd]: (value: number) => - new Angular(value, Unit.InchesPer100Yd), + [Unit.InchesPer100Yd]: (value: number) => new Angular(value, Unit.InchesPer100Yd), [Unit.CmPer100M]: (value: number) => new Angular(value, Unit.CmPer100M), [Unit.OClock]: (value: number) => new Angular(value, Unit.OClock), [Unit.Inch]: (value: number) => new Distance(value, Unit.Inch), [Unit.Foot]: (value: number) => new Distance(value, Unit.Foot), [Unit.Yard]: (value: number) => new Distance(value, Unit.Yard), [Unit.Mile]: (value: number) => new Distance(value, Unit.Mile), - [Unit.NauticalMile]: (value: number) => - new Distance(value, Unit.NauticalMile), + [Unit.NauticalMile]: (value: number) => new Distance(value, Unit.NauticalMile), [Unit.Millimeter]: (value: number) => new Distance(value, Unit.Millimeter), [Unit.Centimeter]: (value: number) => new Distance(value, Unit.Centimeter), [Unit.Meter]: (value: number) => new Distance(value, Unit.Meter), @@ -697,8 +770,7 @@ const UNew = { [Unit.Bar]: (value: number) => new Pressure(value, Unit.Bar), [Unit.hPa]: (value: number) => new Pressure(value, Unit.hPa), [Unit.PSI]: (value: number) => new Pressure(value, Unit.PSI), - [Unit.Fahrenheit]: (value: number) => - new Temperature(value, Unit.Fahrenheit), + [Unit.Fahrenheit]: (value: number) => new Temperature(value, Unit.Fahrenheit), [Unit.Celsius]: (value: number) => new Temperature(value, Unit.Celsius), [Unit.Kelvin]: (value: number) => new Temperature(value, Unit.Kelvin), [Unit.Rankin]: (value: number) => new Temperature(value, Unit.Rankin), @@ -731,41 +803,36 @@ const UNew = { * @throws {TypeError} If the `instance` is not a `number` and not an instance of the `expectedClass`, * indicating an unsupported type for coercion. */ -function unitTypeCoerce( +function unitTypeCoerce>( instance: number | T, - expectedClass: new (value: number, unit: Unit) => T, // Better type for constructor - defaultUnit: Unit, + expectedClass: new (value: number, unit: AllowedUnitT) => T, + defaultUnit: AllowedUnitT ): T { if (instance instanceof expectedClass) { - // If the instance is already of the expected class type, return it. return instance; } else if (typeof instance === "number") { - // If the instance is a number, create a new instance using the default unit. return new expectedClass(instance, defaultUnit); } else { - // If the instance is not of the expected type, throw a TypeError. - throw new TypeError( - `Instance must be a type of ${expectedClass.name} or 'number'`, - ); + throw new TypeError(`Instance must be a type of ${expectedClass.name} or 'number'`); } } export interface IPreferredUnits { - angular: Unit; - distance: Unit; - velocity: Unit; - pressure: Unit; - temperature: Unit; - diameter: Unit; - length: Unit; - weight: Unit; - adjustment: Unit; - drop: Unit; - energy: Unit; - ogw: Unit; - sight_height: Unit; - target_height: Unit; - twist: Unit; + angular: AngularUnit; + distance: DistanceUnit; + velocity: VelocityUnit; + pressure: PressureUnit; + temperature: TemperatureUnit; + diameter: DistanceUnit; + length: DistanceUnit; + weight: WeightUnit; + adjustment: AngularUnit; + drop: DistanceUnit; + energy: EnergyUnit; + ogw: WeightUnit; + sight_height: DistanceUnit; + target_height: DistanceUnit; + twist: DistanceUnit; defaults(): void; setUnits(units: Partial): void; } @@ -776,21 +843,21 @@ function isUnit(value: any): value is Unit { } export class PreferredUnits implements IPreferredUnits { - angular: Unit = Unit.Degree; - distance: Unit = Unit.Yard; - velocity: Unit = Unit.FPS; - pressure: Unit = Unit.InHg; - temperature: Unit = Unit.Fahrenheit; - diameter: Unit = Unit.Inch; - length: Unit = Unit.Inch; - weight: Unit = Unit.Grain; - adjustment: Unit = Unit.MIL; - drop: Unit = Unit.Inch; - energy: Unit = Unit.FootPound; - ogw: Unit = Unit.Pound; - sight_height: Unit = Unit.Inch; - target_height: Unit = Unit.Inch; - twist: Unit = Unit.Inch; + angular: AngularUnit = Unit.Degree; + distance: DistanceUnit = Unit.Yard; + velocity: VelocityUnit = Unit.FPS; + pressure: PressureUnit = Unit.InHg; + temperature: TemperatureUnit = Unit.Fahrenheit; + diameter: DistanceUnit = Unit.Inch; + length: DistanceUnit = Unit.Inch; + weight: WeightUnit = Unit.Grain; + adjustment: AngularUnit = Unit.MIL; + drop: DistanceUnit = Unit.Inch; + energy: EnergyUnit = Unit.FootPound; + ogw: WeightUnit = Unit.Pound; + sight_height: DistanceUnit = Unit.Inch; + target_height: DistanceUnit = Unit.Inch; + twist: DistanceUnit = Unit.Inch; defaults(): void { this.angular = Unit.Degree; @@ -824,7 +891,7 @@ export class PreferredUnits implements IPreferredUnits { const preferredUnits = new PreferredUnits(); export { - AbstractUnit, + Dimension, Angular, Distance, Velocity, @@ -833,6 +900,13 @@ export { Pressure, Energy, Unit, + type AngularUnit, + type DistanceUnit, + type VelocityUnit, + type WeightUnit, + type TemperatureUnit, + type EnergyUnit, + type PressureUnit, UnitProps, unitTypeCoerce, UNew, diff --git a/src/vector.ts b/src/vector.ts index 673f277..1882c1e 100644 --- a/src/vector.ts +++ b/src/vector.ts @@ -2,22 +2,22 @@ class Vector { constructor( public x: number, public y: number, - public z: number, + public z: number ) {} copy() { return new Vector(this.x, this.y, this.z); } - magnitude(): number { + mag(): number { return Math.hypot(this.x, this.y, this.z); } - mulByConst(a: number): Vector { + mul(a: number): Vector { return new Vector(this.x * a, this.y * a, this.z * a); } - mulByVector(b: Vector): number { + dot(b: Vector): number { return this.x * b.x + this.y * b.y + this.z * b.z; } @@ -25,20 +25,20 @@ class Vector { return new Vector(this.x + b.x, this.y + b.y, this.z + b.z); } - subtract(b: Vector): Vector { + sub(b: Vector): Vector { return new Vector(this.x - b.x, this.y - b.y, this.z - b.z); } - negate(): Vector { + neg(): Vector { return new Vector(-this.x, -this.y, -this.z); } - normalize(): Vector { - const m: number = this.magnitude(); + norm(): Vector { + const m: number = this.mag(); if (Math.abs(m) < 1e-10) { return new Vector(this.x, this.y, this.z); } - return this.mulByConst(1.0 / m); + return this.mul(1.0 / m); } static sum(...vectors: Vector[]): Vector { diff --git a/todo.md b/todo.md index 0db46ee..b380213 100644 --- a/todo.md +++ b/todo.md @@ -3,14 +3,14 @@ - [x] constants - [x] vector - [x] munition - - [x] weapon - - [x] ammo - - [ ] sight ? + - [x] weapon + - [x] ammo + - [ ] sight ? - [x] conditions - - [x] atmo - - [x] vacuum - - [x] wind - - [x] shot + - [x] atmo + - [x] vacuum + - [x] wind + - [x] shot - [x] drag model - [x] exceptions - [ ] helpers (partially) @@ -22,17 +22,16 @@ - [x] trajectory data - [x] unit - - [tests] - - [x] atmosphere - - [x] computer - - [ч] danger space - - [ ] helpers - - [ ] incomplete shots - - [ ] issues - - [x] mbc - - [ ] sight ? - - [x] trajectory - - [x] units - - [x] vector - - [x] zero finding \ No newline at end of file + - [x] atmosphere + - [x] computer + - [ч] danger space + - [ ] helpers + - [ ] incomplete shots + - [ ] issues + - [x] mbc + - [ ] sight ? + - [x] trajectory + - [x] units + - [x] vector + - [x] zero finding diff --git a/tsconfig.json b/tsconfig.json index 76164c6..0e3384a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,8 @@ { - "include": [ - "src/", - ], - "exclude": [ - "node_modules", - ], + "include": ["src/", "build/**/*.d.ts", "wasm/bclibc.d.ts"], + "exclude": ["node_modules"], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ @@ -15,10 +10,9 @@ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ @@ -29,39 +23,43 @@ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "es6", /* Specify what module code is generated. */ + "module": "ESNext" /* Specify what module code is generated. */, // "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, + "paths": { + "@wasm/*": ["build/*"], + "@src/*": ["src/*"], + "@dist/*": ["dist/*"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": [ + "emscripten", + "node" + ] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true /* Enable importing .json files. */, // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "emitDeclarationOnly": true /* Only output d.ts files and not JavaScript files. */, // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ @@ -78,22 +76,20 @@ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */ + "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */, // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ @@ -107,9 +103,8 @@ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..e4a719b --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "tsup"; +import path from "path"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + dts: true, + splitting: false, + clean: true, + shims: true, + noExternal: ["../build/bclibc.js", /bclibc/], + bundle: true, + esbuildOptions(options) { + options.alias = { + "@wasm": path.resolve(__dirname, "./build"), + }; + options.mainFields = ["module", "main"]; + }, +}); diff --git a/wasm/bindings.cpp b/wasm/bindings.cpp new file mode 100644 index 0000000..0c09ab2 --- /dev/null +++ b/wasm/bindings.cpp @@ -0,0 +1,861 @@ +// wasm/bindings.cpp +#include +#include +#include "bclibc.hpp" + +using namespace emscripten; +using namespace bclibc; + +constexpr double APEX_IS_MAX_RANGE_RADIANS = 0.0003; +constexpr double ALLOWED_ZERO_ERROR_FEET = 1e-2; + +// ============================================================================ +// Type Aliases +// ============================================================================ + +using DragTableIface = val; +using WindsIfaceList = val; + +enum class IntegrationMethod +{ + RK4, + EULER +}; + +struct DragTablePoint +{ +public: + double Mach; + double CD; +}; + +struct ShotPropsInput +{ +public: + double bc; + double look_angle_rad; + double twist_inch; + double length_inch; + double diameter_inch; + double weight_grain; + double barrel_elevation_rad; + double barrel_azimuth_rad; + double sight_height_ft; + double cant_angle_rad; + double alt0_ft; + double muzzle_velocity_fps; + DragTableIface drag_table; + BCLIBC_Atmosphere atmo; + BCLIBC_Coriolis coriolis; + WindsIfaceList winds; + // options + IntegrationMethod method; + BCLIBC_Config config; +}; + +struct TrajectoryRequest +{ +public: + double range_limit_ft; + double range_step_ft; + double time_step; + bool dense_output; + BCLIBC_TrajFlag filter_flags; +}; + +struct Interception +{ +public: + BCLIBC_BaseTrajData raw_data; + BCLIBC_TrajectoryData full_data; +}; + +struct HitOutput +{ +public: + val trajectory = val::array(); + val dense_trajectory = val::array(); + BCLIBC_TerminationReason reason = BCLIBC_TerminationReason(); +}; + +inline static BCLIBC_WindSock windSockFromVal(const WindsIfaceList &winds) +{ + BCLIBC_WindSock sock; + + if (winds.isUndefined() || winds.isNull() || !winds.isArray()) + { + return sock; + } + + try + { + unsigned length = winds["length"].as(); + for (unsigned i = 0; i < length; i++) + { + sock.push(winds[i].as()); + } + } + catch (...) + { + std::invalid_argument("Array of Winds has invalid format"); + } + sock.update_cache(); + return sock; +} + +inline static BCLIBC_MachList machListFromVal(const DragTableIface &drag_table) +{ + BCLIBC_MachList list; + + if (!drag_table.isUndefined() && !drag_table.isNull()) + { + unsigned length = drag_table["length"].as(); + for (unsigned i = 0; i < length; i++) + { + auto point = drag_table[i]; + list.push_back(point["Mach"].as()); + } + } + + return list; +} + +// ============================================================================ +// Helper: PCHIP Curve Generation +// ============================================================================ + +/** + * @brief Creates a BCLIBC_Curve from JavaScript array of {Mach, CD} objects + * using PCHIP (Piecewise Cubic Hermite Interpolating Polynomial) algorithm. + */ +inline static BCLIBC_Curve curveFromVal(const DragTableIface &drag_table) +{ + if (drag_table.isUndefined() || drag_table.isNull()) + { + throw std::invalid_argument("Curve data is undefined or null"); + } + + unsigned n = drag_table["length"].as(); + if (n < 2) + { + throw std::invalid_argument("BCLIBC_Curve requires at least 2 data points"); + } + + std::vector x(n); + std::vector y(n); + + for (unsigned i = 0; i < n; ++i) + { + auto item = drag_table[i]; + x[i] = item["Mach"].as(); + y[i] = item["CD"].as(); + } + + return bclibc::build_pchip_curve_from_arrays(x, y); +} + +double interpolate2pt( + double x, double x0, double y0, double x1, double y1) +{ + double result; + BCLIBC_InterpStatus status = BCLIBC_interpolate2pt(x, x0, y0, x1, y1, result); + + if (status != BCLIBC_InterpStatus::SUCCESS) + { + throw std::domain_error("Zero division error during interpolation"); + } + return result; +} + +inline static double selectCalcStep(const IntegrationMethod method) +{ + + switch (method) + { + case IntegrationMethod::RK4: + return 0.0025; + break; + case IntegrationMethod::EULER: + return 0.5; + break; + default: + throw std::invalid_argument("Unknown integration method"); + break; + } +} + +BCLIBC_IntegrateCallable selectIntegrationMethod(const IntegrationMethod &method) +{ + switch (method) + { + case IntegrationMethod::RK4: + return BCLIBC_integrateRK4; + break; + case IntegrationMethod::EULER: + return BCLIBC_integrateEULER; + break; + default: + throw std::invalid_argument("Unknown integration method"); + break; + } +} + +BCLIBC_ShotProps shotPropsFromVal(const ShotPropsInput &props) +{ + return BCLIBC_ShotProps( + props.bc, + props.look_angle_rad, + props.twist_inch, + props.length_inch, + props.diameter_inch, + props.weight_grain, + props.barrel_elevation_rad, + props.barrel_azimuth_rad, + props.sight_height_ft, + std::cos(props.cant_angle_rad), + std::sin(props.cant_angle_rad), + props.alt0_ft, + selectCalcStep(props.method) * props.config.cStepMultiplier, + props.muzzle_velocity_fps, + 0.0, + curveFromVal(props.drag_table), + machListFromVal(props.drag_table), + props.atmo, + props.coriolis, + windSockFromVal(props.winds), + BCLIBC_TRAJ_FLAG_NONE); +} + +// ============================================================================ +// Exception Handling Utilities +// ============================================================================ + +/** + * @brief Creates a JavaScript Error constructor from the global scope + * @param className Name of the Error class (e.g., "SolverRuntimeError") + * @return JavaScript Error constructor function + */ +inline static val getErrorConstructor(const std::string &className) +{ + // Try to get custom error class from globalThis, fall back to Error + try + { + val globalThis = val::global("globalThis"); + if (!globalThis.isUndefined()) + { + val errorClass = globalThis[className]; + // Check if it's a function by trying to use it as a constructor + if (!errorClass.isUndefined()) + { + return errorClass; + } + } + } + catch (...) + { + // Fall back to standard Error + } + return val::global("Error"); +} + +/** + * @brief Converts C++ exception to JavaScript Error with proper type + * @param e C++ exception + * @param errorType JavaScript error type name + */ +template +inline static void throwAsJsError(const ExceptionType &e, const std::string &errorType) +{ + val ErrorClass = getErrorConstructor(errorType); + // Call the constructor with 'new' keyword + val error = ErrorClass.new_(std::string(e.what())); + // Set prototype to ensure instanceof works + error.set("__proto__", ErrorClass["prototype"]); + error.set("name", val(errorType)); + error.throw_(); +} + +/** + * @brief Converts BCLIBC_OutOfRangeError to JavaScript OutOfRangeError + */ +inline static void throwOutOfRangeError(const BCLIBC_OutOfRangeError &e) +{ + val ErrorClass = getErrorConstructor("OutOfRangeError"); + val error = ErrorClass.new_(std::string(e.what())); + error.set("__proto__", ErrorClass["prototype"]); + error.set("name", val("OutOfRangeError")); + error.set("requestedDistanceFt", e.requested_distance_ft); + error.set("maxRangeFt", e.max_range_ft); + error.set("lookAngleRad", e.look_angle_rad); + error.throw_(); +} + +/** + * @brief Converts BCLIBC_ZeroFindingError to JavaScript ZeroFindingError + */ +inline static void throwZeroFindingError(const BCLIBC_ZeroFindingError &e) +{ + val ErrorClass = getErrorConstructor("ZeroFindingError"); + val error = ErrorClass.new_(std::string(e.what())); + error.set("__proto__", ErrorClass["prototype"]); + error.set("name", val("ZeroFindingError")); + error.set("zeroFindingError", e.zero_finding_error); + error.set("iterationsCount", e.iterations_count); + error.set("lastBarrelElevationRad", e.last_barrel_elevation_rad); + error.throw_(); +} + +/** + * @brief Converts BCLIBC_InterceptionError to JavaScript InterceptionError + */ +inline static void throwInterceptionError(const BCLIBC_InterceptionError &e) +{ + val ErrorClass = getErrorConstructor("InterceptionError"); + val error = ErrorClass.new_(std::string(e.what())); + error.set("__proto__", ErrorClass["prototype"]); + error.set("name", val("InterceptionError")); + // TODO: Convert rawData and fullData to JS objects if needed + error.throw_(); +} + +/** + * @brief Universal exception handler wrapper template + * Wraps any callable and converts C++ exceptions to JavaScript errors + */ +template +inline static auto wrapExceptions(Func &&func) -> decltype(func()) +{ + try + { + return func(); + } + catch (const BCLIBC_OutOfRangeError &e) + { + throwOutOfRangeError(e); + throw; // unreachable, but needed for compiler + } + catch (const BCLIBC_ZeroFindingError &e) + { + throwZeroFindingError(e); + throw; // unreachable, but needed for compiler + } + catch (const BCLIBC_InterceptionError &e) + { + throwInterceptionError(e); + throw; // unreachable, but needed for compiler + } + catch (const BCLIBC_SolverRuntimeError &e) + { + throwAsJsError(e, "SolverRuntimeError"); + throw; // unreachable, but needed for compiler + } + catch (const std::exception &e) + { + // Generic fallback for any other std::exception + val error = val::global("Error").new_(std::string(e.what())); + error.throw_(); + throw; // unreachable, but needed for compiler + } +} + +inline static void initEngine(BCLIBC_BaseEngine &engine, const ShotPropsInput &shotProps) +{ + engine.integrate_func = selectIntegrationMethod(shotProps.method); + engine.config = shotProps.config; + engine.shot = shotPropsFromVal(shotProps); + engine.gravity_vector = BCLIBC_V3dT(0.0, engine.config.cGravityConstant, 0.0); +} + +inline static BCLIBC_TrajectoryData findApex(const ShotPropsInput &shotProps) +{ + return wrapExceptions([&]() + { + BCLIBC_BaseEngine engine; + initEngine(engine, shotProps); + + BCLIBC_BaseTrajData apex; + engine.find_apex(apex); + + return BCLIBC_TrajectoryData(engine.shot, apex, BCLIBC_TRAJ_FLAG_APEX); }); +} + +inline static BCLIBC_MaxRangeResult findMaxRange(const ShotPropsInput &shotProps, double low_angle_deg, double high_angle_deg) +{ + return wrapExceptions([&]() + { + BCLIBC_BaseEngine engine; + initEngine(engine, shotProps); + + return engine.find_max_range( + low_angle_deg, + high_angle_deg, + APEX_IS_MAX_RANGE_RADIANS); }); +} + +inline static double findZeroAngle(const ShotPropsInput &shotProps, double distance) +{ + return wrapExceptions([&]() + { + BCLIBC_BaseEngine engine; + initEngine(engine, shotProps); + return engine.zero_angle_with_fallback( + distance, + APEX_IS_MAX_RANGE_RADIANS, + ALLOWED_ZERO_ERROR_FEET); }); +} + +inline static HitOutput integrate(const ShotPropsInput &shotProps, const TrajectoryRequest &request) +{ + return wrapExceptions([&]() + { + HitOutput obj = HitOutput(); + + std::vector filtered_records; + BCLIBC_BaseTrajSeq dense_trajectory; + + BCLIBC_BaseEngine engine; + initEngine(engine, shotProps); + + engine.integrate_filtered( + request.range_limit_ft, + request.range_step_ft, + request.time_step, + request.filter_flags, + filtered_records, + obj.reason, + request.dense_output ? &dense_trajectory : nullptr); + + for (const auto &row : filtered_records) + { + obj.trajectory.call("push", row); + } + + if (request.dense_output) + { + for (ssize_t i = 0; i < dense_trajectory.get_length(); ++i) + { + obj.dense_trajectory.call("push", dense_trajectory[i]); + } + } + return obj; }); +} + +inline static Interception integrateRawAt(const ShotPropsInput &shotProps, const BCLIBC_BaseTrajData_InterpKey &key, const double &target_value) +{ + return wrapExceptions([&]() + { + BCLIBC_TerminationReason reason; + BCLIBC_BaseTrajSeq dense_trajectory; + + BCLIBC_BaseEngine engine; + initEngine(engine, shotProps); + Interception result = {}; + + engine.integrate_at(key, target_value, result.raw_data, result.full_data); + return result; }); +} + +inline static bool get_flat_fire_only(const BCLIBC_Coriolis &obj) +{ + return obj.flat_fire_only != 0; +} + +inline static void set_flat_fire_only(BCLIBC_Coriolis &obj, bool val) +{ + obj.flat_fire_only = val ? 1 : 0; +} + +inline static val get_position(const BCLIBC_BaseTrajData &d) +{ + val v = val::object(); + v.set("x", d.px); + v.set("y", d.py); + v.set("z", d.pz); + return v; +} + +inline static void set_position(BCLIBC_BaseTrajData &d, const val &v) +{ + d.px = v["x"].as(); + d.py = v["y"].as(); + d.pz = v["z"].as(); +} + +inline static val get_velocity(const BCLIBC_BaseTrajData &d) +{ + val v = val::object(); + v.set("x", d.vx); + v.set("y", d.vy); + v.set("z", d.vz); + return v; +} + +inline static void set_velocity(BCLIBC_BaseTrajData &d, const val &v) +{ + d.vx = v["x"].as(); + d.vy = v["y"].as(); + d.vz = v["z"].as(); +} + +inline static BCLIBC_BaseTrajData interpolateBaseTrajData( + BCLIBC_BaseTrajData_InterpKey key_kind, + double key_value, + const BCLIBC_BaseTrajData &p0, + const BCLIBC_BaseTrajData &p1, + const BCLIBC_BaseTrajData &p2) +{ + BCLIBC_BaseTrajData out; + BCLIBC_BaseTrajData::interpolate(key_kind, key_value, p0, p1, p2, out); + return out; +}; + +// ============================================================================ +// Test Functions for Exception Handling +// ============================================================================ + +/** + * @brief Custom C++ exception class for testing + * Inherits from std::runtime_error and adds custom fields + */ +class TestCustomException : public std::runtime_error +{ +public: + double customValue; + int customCount; + + TestCustomException(const std::string &message, double value, int count) + : std::runtime_error(message), customValue(value), customCount(count) {} +}; + +/** + * @brief Test function that throws std::runtime_error as JavaScript Error + * Used to verify C++ exception to JavaScript exception conversion + */ +inline static void testThrowRuntimeError(const std::string &message) +{ + // Create and throw JavaScript Error object directly + val error = val::global("Error").new_(message); + error.throw_(); +} + +/** + * @brief Test function that throws BCLIBC_SolverRuntimeError for testing + * This will be caught by wrapExceptions and converted to JS SolverRuntimeError + */ +inline static void testThrowSolverError(const std::string &message) +{ + wrapExceptions([&]() + { + throw BCLIBC_SolverRuntimeError(message); + return; // needed for template deduction + }); +} + +/** + * @brief Test function that throws custom C++ exception with additional fields + * Converts custom exception to JavaScript Error with properties + */ +inline static void testThrowCustomException(const std::string &message, double value, int count) +{ + try + { + throw TestCustomException(message, value, count); + } + catch (const TestCustomException &e) + { + // Create JavaScript Error with custom properties + val error = val::global("Error").new_(std::string(e.what())); + error.set("customValue", e.customValue); + error.set("customCount", e.customCount); + error.throw_(); + } +} + +EMSCRIPTEN_BINDINGS(bclibc) +{ + // ======================================================================== + // Constants + // ======================================================================== + + constant("APEX_IS_MAX_RANGE_RADIANS", APEX_IS_MAX_RANGE_RADIANS); + constant("ALLOWED_ZERO_ERROR_FEET", ALLOWED_ZERO_ERROR_FEET); + + // ======================================================================== + // Enums + // ======================================================================== + + enum_("_InterpMethod", enum_value_type::number) + .value("PCHIP", BCLIBC_InterpMethod::PCHIP) + .value("LINEAR", BCLIBC_InterpMethod::LINEAR); + + enum_("_TerminationReason", enum_value_type::number) + .value("NO_TERMINATE", BCLIBC_TerminationReason::NO_TERMINATE) + .value("TARGET_RANGE_REACHED", BCLIBC_TerminationReason::TARGET_RANGE_REACHED) + .value("MINIMUM_VELOCITY_REACHED", BCLIBC_TerminationReason::MINIMUM_VELOCITY_REACHED) + .value("MAXIMUM_DROP_REACHED", BCLIBC_TerminationReason::MAXIMUM_DROP_REACHED) + .value("MINIMUM_ALTITUDE_REACHED", BCLIBC_TerminationReason::MINIMUM_ALTITUDE_REACHED) + .value("HANDLER_REQUESTED_STOP", BCLIBC_TerminationReason::HANDLER_REQUESTED_STOP); + + enum_("_TrajFlag", enum_value_type::number) + .value("NONE", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_NONE) + .value("ZERO_UP", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_ZERO_UP) + .value("ZERO_DOWN", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_ZERO_DOWN) + .value("ZERO", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_ZERO) + .value("MACH", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_MACH) + .value("RANGE", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_RANGE) + .value("APEX", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_APEX) + .value("ALL", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_ALL) + .value("MRT", BCLIBC_TrajFlag::BCLIBC_TRAJ_FLAG_MRT); + + enum_("_IntegrationMethod", enum_value_type::number) + .value("RK4", IntegrationMethod::RK4) + .value("EULER", IntegrationMethod::EULER); + + enum_("_BaseTrajDataInterpKey", enum_value_type::number) + .value("TIME", BCLIBC_BaseTrajData_InterpKey::TIME) + .value("POS_X", BCLIBC_BaseTrajData_InterpKey::POS_X) + .value("POS_Y", BCLIBC_BaseTrajData_InterpKey::POS_Y) + .value("POS_Z", BCLIBC_BaseTrajData_InterpKey::POS_Z) + .value("VEL_X", BCLIBC_BaseTrajData_InterpKey::VEL_X) + .value("VEL_Y", BCLIBC_BaseTrajData_InterpKey::VEL_Y) + .value("VEL_Z", BCLIBC_BaseTrajData_InterpKey::VEL_Z) + .value("MACH", BCLIBC_BaseTrajData_InterpKey::MACH); + + enum_("_TrajectoryDataInterpKey", enum_value_type::number) + .value("TIME", BCLIBC_TrajectoryData_InterpKey::TIME) + .value("DISTANCE", BCLIBC_TrajectoryData_InterpKey::DISTANCE) + .value("VELOCITY", BCLIBC_TrajectoryData_InterpKey::VELOCITY) + .value("MACH", BCLIBC_TrajectoryData_InterpKey::MACH) + .value("HEIGHT", BCLIBC_TrajectoryData_InterpKey::HEIGHT) + .value("SLANT_HEIGHT", BCLIBC_TrajectoryData_InterpKey::SLANT_HEIGHT) + .value("DROP_ANGLE", BCLIBC_TrajectoryData_InterpKey::DROP_ANGLE) + .value("WINDAGE", BCLIBC_TrajectoryData_InterpKey::WINDAGE) + .value("WINDAGE_ANGLE", BCLIBC_TrajectoryData_InterpKey::WINDAGE_ANGLE) + .value("SLANT_DISTANCE", BCLIBC_TrajectoryData_InterpKey::SLANT_DISTANCE) + .value("ANGLE", BCLIBC_TrajectoryData_InterpKey::ANGLE) + .value("DENSITY_RATIO", BCLIBC_TrajectoryData_InterpKey::DENSITY_RATIO) + .value("DRAG", BCLIBC_TrajectoryData_InterpKey::DRAG) + .value("ENERGY", BCLIBC_TrajectoryData_InterpKey::ENERGY) + .value("OGW", BCLIBC_TrajectoryData_InterpKey::OGW) + .value("FLAG", BCLIBC_TrajectoryData_InterpKey::FLAG); + + value_object("_Config") + .field("stepMultiplier", &BCLIBC_Config::cStepMultiplier) + .field("zeroFindingAccuracy", &BCLIBC_Config::cZeroFindingAccuracy) + .field("minimumVelocity", &BCLIBC_Config::cMinimumVelocity) + .field("maximumDrop", &BCLIBC_Config::cMaximumDrop) + .field("maxIterations", &BCLIBC_Config::cMaxIterations) + .field("gravityConstant", &BCLIBC_Config::cGravityConstant) + .field("minimumAltitude", &BCLIBC_Config::cMinimumAltitude); + + value_object("_Atmosphere") + .field("t0", &BCLIBC_Atmosphere::_t0) + .field("a0", &BCLIBC_Atmosphere::_a0) + .field("p0", &BCLIBC_Atmosphere::_p0) + .field("mach", &BCLIBC_Atmosphere::_mach) + .field("density_ratio", &BCLIBC_Atmosphere::density_ratio) + .field("cLowestTempC", &BCLIBC_Atmosphere::cLowestTempC); + + value_object("_Wind") + .field("velocity_fps", &BCLIBC_Wind::velocity) + .field("direction_from_rad", &BCLIBC_Wind::direction_from) + .field("until_distance_ft", &BCLIBC_Wind::until_distance) + .field("MAX_DISTANCE_FEET", &BCLIBC_Wind::MAX_DISTANCE_FEET); + + value_object("_Coriolis") + .field("sin_lat", &BCLIBC_Coriolis::sin_lat) + .field("cos_lat", &BCLIBC_Coriolis::cos_lat) + .field("sin_az", &BCLIBC_Coriolis::sin_az) + .field("cos_az", &BCLIBC_Coriolis::cos_az) + .field("range_east", &BCLIBC_Coriolis::range_east) + .field("range_north", &BCLIBC_Coriolis::range_north) + .field("cross_east", &BCLIBC_Coriolis::cross_east) + .field("cross_north", &BCLIBC_Coriolis::cross_north) + .field("flat_fire_only", &get_flat_fire_only, &set_flat_fire_only) + .field("muzzle_velocity_fps", &BCLIBC_Coriolis::muzzle_velocity_fps); + + value_object("_DragTablePoint") + .field("Mach", &DragTablePoint::Mach) + .field("CD", &DragTablePoint::CD); + + value_object("_ShotPropsInput") + .field("bc", &ShotPropsInput::bc) + .field("look_angle_rad", &ShotPropsInput::look_angle_rad) + .field("twist_inch", &ShotPropsInput::twist_inch) + .field("length_inch", &ShotPropsInput::length_inch) + .field("diameter_inch", &ShotPropsInput::diameter_inch) + .field("weight_grain", &ShotPropsInput::weight_grain) + .field("barrel_elevation_rad", &ShotPropsInput::barrel_elevation_rad) + .field("barrel_azimuth_rad", &ShotPropsInput::barrel_azimuth_rad) + .field("sight_height_ft", &ShotPropsInput::sight_height_ft) + .field("cant_angle_rad", &ShotPropsInput::cant_angle_rad) + .field("alt0_ft", &ShotPropsInput::alt0_ft) + .field("muzzle_velocity_fps", &ShotPropsInput::muzzle_velocity_fps) + .field("drag_table", &ShotPropsInput::drag_table) + .field("atmo", &ShotPropsInput::atmo) + .field("coriolis", &ShotPropsInput::coriolis) + .field("winds", &ShotPropsInput::winds) + .field("method", &ShotPropsInput::method) + .field("config", &ShotPropsInput::config); + + value_object("_TrajectoryRequest") + .field("range_limit_ft", &TrajectoryRequest::range_limit_ft) + .field("range_step_ft", &TrajectoryRequest::range_step_ft) + .field("time_step", &TrajectoryRequest::time_step) + .field("dense_output", &TrajectoryRequest::dense_output) + .field("filter_flags", &TrajectoryRequest::filter_flags); + + value_object("_BaseTrajData") + .field("time", &BCLIBC_BaseTrajData::time) + .field("px", &BCLIBC_BaseTrajData::px) + .field("py", &BCLIBC_BaseTrajData::py) + .field("pz", &BCLIBC_BaseTrajData::pz) + .field("vx", &BCLIBC_BaseTrajData::vx) + .field("vy", &BCLIBC_BaseTrajData::vy) + .field("vz", &BCLIBC_BaseTrajData::vz) + .field("mach", &BCLIBC_BaseTrajData::mach) + .field("position", get_position, set_position) + .field("velocity", get_velocity, set_velocity); + + value_object("_MaxRangeResult") + .field("angle_at_max_rad", &BCLIBC_MaxRangeResult::angle_at_max_rad) + .field("max_range_ft", &BCLIBC_MaxRangeResult::max_range_ft); + + value_object("_TrajectoryData") + .field("time", &BCLIBC_TrajectoryData::time) + .field("distance_ft", &BCLIBC_TrajectoryData::distance_ft) + .field("velocity_fps", &BCLIBC_TrajectoryData::velocity_fps) + .field("mach", &BCLIBC_TrajectoryData::mach) + .field("height_ft", &BCLIBC_TrajectoryData::height_ft) + .field("slant_height_ft", &BCLIBC_TrajectoryData::slant_height_ft) + .field("drop_angle_rad", &BCLIBC_TrajectoryData::drop_angle_rad) + .field("windage_ft", &BCLIBC_TrajectoryData::windage_ft) + .field("windage_angle_rad", &BCLIBC_TrajectoryData::windage_angle_rad) + .field("slant_distance_ft", &BCLIBC_TrajectoryData::slant_distance_ft) + .field("angle_rad", &BCLIBC_TrajectoryData::angle_rad) + .field("density_ratio", &BCLIBC_TrajectoryData::density_ratio) + .field("drag", &BCLIBC_TrajectoryData::drag) + .field("energy_ft_lb", &BCLIBC_TrajectoryData::energy_ft_lb) + .field("ogw_lb", &BCLIBC_TrajectoryData::ogw_lb) + .field("flag", &BCLIBC_TrajectoryData::flag); + + value_object("_Interception") + .field("raw_data", &Interception::raw_data) + .field("full_data", &Interception::full_data); + + value_object("_HitOutput") + .field("trajectory", &HitOutput::trajectory) + .field("dense_trajectory", &HitOutput::dense_trajectory) + .field("reason", &HitOutput::reason); + + register_vector("_WindList"); + register_vector("_DragTable"); + + // ======================================================================== + // Functions + // ======================================================================== + + // The functions bellow can raise custom exceptions + function("findApex", &findApex); + function("findMaxRange", &findMaxRange); + function("findZeroAngle", &findZeroAngle); + function("integrate", &integrate); + function("integrateRawAt", &integrateRawAt); + + function("interpolateBasetrajData", &interpolateBaseTrajData); + function("interpolateTrajectoryData", &BCLIBC_TrajectoryData::interpolate); + + // ======================================================================== + // Utility Functions + // ======================================================================== + + function("getCorrection", &BCLIBC_getCorrection); + function("calculateEnergy", &BCLIBC_calculateEnergy); + function("calculateOgw", &BCLIBC_calculateOgw); + + // ======================================================================== + // Test Functions (for exception handling verification) + // ======================================================================== + + function("testThrowRuntimeError", &testThrowRuntimeError); + function("testThrowCustomException", &testThrowCustomException); + function("testThrowSolverError", &testThrowSolverError); + + // ======================================================================== + // Interpolation Functions + // ======================================================================== + + // Hermite interpolation + function("hermite", &BCLIBC_hermite); + + // 3-point PCHIP interpolation + function("interpolate3pt", &BCLIBC_interpolate3pt); + + // 2-point linear interpolation (wrapper для out parameter) + function("interpolate2pt", &interpolate2pt); + + // ======================================================================== + // Vector Class + // ======================================================================== + + // Реєструємо BCLIBC_V3dT як клас (не value_object!) + class_("_Vector") + // Конструктори + .constructor<>() + .constructor() + + // Публічні поля як властивості + .property("x", &BCLIBC_V3dT::x) + .property("y", &BCLIBC_V3dT::y) + .property("z", &BCLIBC_V3dT::z) + + // Арифметичні оператори (створюють нові вектори) + .function("add", select_overload(&BCLIBC_V3dT::operator+)) + .function("sub", select_overload(&BCLIBC_V3dT::operator-)) + .function("neg", select_overload(&BCLIBC_V3dT::operator-)) + // .function("neg", optional_override([](const BCLIBC_V3dT& self) { + // return -self; + // })) + .function("mul", select_overload(&BCLIBC_V3dT::operator*)) + .function("div", select_overload(&BCLIBC_V3dT::operator/)) + .function("dot", select_overload(&BCLIBC_V3dT::operator*)) + + // NOTE: NOT RECOMMENDED FOR ESC! USE WRAPPERS THAT RETURNS VOID + // In-place оператори (змінюють вектор) + // .function("iadd", &BCLIBC_V3dT::operator+=) + // .function("isub", &BCLIBC_V3dT::operator-=) + // .function("imul", &BCLIBC_V3dT::operator*=) + // .function("idiv", &BCLIBC_V3dT::operator/=) + + // In-place оператори (safe: void, без reference) + .function("iadd", optional_override([](BCLIBC_V3dT &self, const BCLIBC_V3dT &other) + { self += other; })) + .function("isub", optional_override([](BCLIBC_V3dT &self, const BCLIBC_V3dT &other) + { self -= other; })) + .function("imul", optional_override([](BCLIBC_V3dT &self, double scalar) + { self *= scalar; })) + .function("idiv", optional_override([](BCLIBC_V3dT &self, double scalar) + { self /= scalar; })) + + // Оптимізовані fused операції + .function("fusedMultiplyAdd", &BCLIBC_V3dT::fused_multiply_add) + .function("fusedMultiplySubtract", &BCLIBC_V3dT::fused_multiply_subtract) + .function("linearCombination", &BCLIBC_V3dT::linear_combination) + .function("linearCombination4", &BCLIBC_V3dT::linear_combination_4) + + // Властивості вектора + .function("mag", &BCLIBC_V3dT::mag) + .function("magSquared", &BCLIBC_V3dT::mag_squared) + .function("norm", &BCLIBC_V3dT::norm) + // NOTE: THIS IS UNSAFE! USE WRAPPER + // .function("inorm", &BCLIBC_V3dT::normalize) + .function("inorm", optional_override([](BCLIBC_V3dT &self) + { self.normalize(); })) + .function("toString", optional_override([](const BCLIBC_V3dT &self) + { return std::string("Vector(") + + std::to_string(self.x) + ", " + + std::to_string(self.y) + ", " + + std::to_string(self.z) + ")"; })) + .function("copy", optional_override([](const BCLIBC_V3dT &self) + { return self; })); +} diff --git a/wasm/post.js b/wasm/post.js new file mode 100644 index 0000000..f3d7bfd --- /dev/null +++ b/wasm/post.js @@ -0,0 +1,154 @@ +// wasm/post.js +// Post-processing script for Emscripten module to improve JavaScript API ergonomics + +// ============================================================================ +// Vector Enhancements: Method chaining for in-place operations +// ============================================================================ +// Make in-place operations chainable for better DX: +// v.iadd(v2).imul(2).inorm() instead of three separate statements + +if (Module._Vector) { + const VectorProto = Module._Vector.prototype; + + // List of in-place methods that should return 'this' for chaining + const chainableMethods = ["iadd", "isub", "imul", "idiv", "inorm"]; + + chainableMethods.forEach((methodName) => { + const originalMethod = VectorProto[methodName]; + if (!originalMethod) return; + + VectorProto[methodName] = function (...args) { + originalMethod.apply(this, args); + return this; // Enable chaining + }; + }); + + // Add convenience methods for common vector operations + + /** + * Clone this vector + * @returns {Vector} New vector with same values + */ + VectorProto.clone = function () { + return new Module._Vector(this.x, this.y, this.z); + }; + + /** + * Set vector components + * @param {number} x + * @param {number} y + * @param {number} z + * @returns {Vector} this for chaining + */ + VectorProto.set = function (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + return this; + }; + + /** + * Check if vector is approximately equal to another (within epsilon) + * @param {Vector} other + * @param {number} epsilon - Default 1e-10 + * @returns {boolean} + */ + VectorProto.equals = function (other, epsilon = 1e-10) { + return ( + Math.abs(this.x - other.x) < epsilon && + Math.abs(this.y - other.y) < epsilon && + Math.abs(this.z - other.z) < epsilon + ); + }; + + /** + * Convert to plain JavaScript object + * @returns {{x: number, y: number, z: number}} + */ + VectorProto.toObject = function () { + return { x: this.x, y: this.y, z: this.z }; + }; + + /** + * Convert to array + * @returns {[number, number, number]} + */ + VectorProto.toArray = function () { + return [this.x, this.y, this.z]; + }; + + /** + * String representation + * @returns {string} + */ + VectorProto.toString = function () { + return `Vector(${this.x.toFixed(6)}, ${this.y.toFixed(6)}, ${this.z.toFixed(6)})`; + }; + + /** + * Static factory: Create vector from array + * @param {[number, number, number]} arr + * @returns {Vector} + */ + Module._Vector.fromArray = function (arr) { + return new Module._Vector(arr[0], arr[1], arr[2]); + }; + + /** + * Static factory: Create vector from object + * @param {{x: number, y: number, z: number}} obj + * @returns {Vector} + */ + Module._Vector.fromObject = function (obj) { + return new Module._Vector(obj.x, obj.y, obj.z); + }; + + /** + * Static: Zero vector + * @returns {Vector} + */ + Module._Vector.zero = function () { + return new Module._Vector(0, 0, 0); + }; + + /** + * Static: Unit vectors + * @returns {Vector} + */ + Module._Vector.unitX = function () { + return new Module._Vector(1, 0, 0); + }; + + Module._Vector.unitY = function () { + return new Module._Vector(0, 1, 0); + }; + + Module._Vector.unitZ = function () { + return new Module._Vector(0, 0, 1); + }; +} + +// // ============================================================================ +// // Engine namespace +// // ============================================================================ + +// Module._Engine = { +// findApex: Module._findApex, +// findMaxRange: Module._findMaxRange, +// findZeroAngle: Module._findZeroAngle +// }; + +// delete Module._findApex; +// delete Module._findMaxRange; +// delete Module._findZeroAngle; + +// ============================================================================ +// Logging +// ============================================================================ + +// if (typeof console !== 'undefined' && console.log) { +// console.log('[BCLIBC] WebAssembly module initialized successfully'); +// console.log('[BCLIBC] Available functions:', Object.keys(Module).filter(k => +// typeof Module[k] === 'function' && !k.startsWith('_') +// ).join(', ')); +// } diff --git a/yarn.lock b/yarn.lock index ea39020..5443c16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,7 +20,16 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7": +"@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== @@ -30,6 +39,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== +"@babel/compat-data@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + "@babel/core@^7.23.9", "@babel/core@^7.27.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" @@ -51,20 +65,20 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" - integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== +"@babel/core@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/generator" "^7.28.6" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-compilation-targets" "^7.28.6" "@babel/helper-module-transforms" "^7.28.6" "@babel/helpers" "^7.28.6" - "@babel/parser" "^7.28.6" + "@babel/parser" "^7.29.0" "@babel/template" "^7.28.6" - "@babel/traverse" "^7.28.6" - "@babel/types" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -105,6 +119,17 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" @@ -178,16 +203,16 @@ regexpu-core "^6.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" - integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== +"@babel/helper-define-polyfill-provider@^0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.7.tgz#8d01cba97de419115ad3497573a476db15dc6c6a" + integrity sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - debug "^4.4.1" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" lodash.debounce "^4.0.8" - resolve "^1.22.10" + resolve "^1.22.11" "@babel/helper-globals@^7.28.0": version "7.28.0" @@ -362,6 +387,13 @@ dependencies: "@babel/types" "^7.28.6" +"@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== + dependencies: + "@babel/types" "^7.29.0" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" @@ -554,14 +586,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz#80cb86d3eaa2102e18ae90dd05ab87bdcad3877d" - integrity sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA== +"@babel/plugin-transform-async-generator-functions@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== dependencies: "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.6" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-async-to-generator@^7.28.6": version "7.28.6" @@ -645,10 +677,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz#e0c59ba54f1655dd682f2edf5f101b5910a8f6f3" - integrity sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1" + integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.28.5" "@babel/helper-plugin-utils" "^7.28.6" @@ -751,15 +783,15 @@ "@babel/helper-module-transforms" "^7.28.6" "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-modules-systemjs@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== +"@babel/plugin-transform-modules-systemjs@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz#e458a95a17807c415924106a3ff188a3b8dee964" + integrity sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ== dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -769,13 +801,13 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" - integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== +"@babel/plugin-transform-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-new-target@^7.27.1": version "7.27.1" @@ -871,10 +903,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz#6ca2ed5b76cff87980f96eaacfc2ce833e8e7a1b" - integrity sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw== +"@babel/plugin-transform-regenerator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== dependencies: "@babel/helper-plugin-utils" "^7.28.6" @@ -971,12 +1003,12 @@ "@babel/helper-create-regexp-features-plugin" "^7.28.5" "@babel/helper-plugin-utils" "^7.28.6" -"@babel/preset-env@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.6.tgz#b4586bb59d8c61be6c58997f4912e7ea6bd17178" - integrity sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw== +"@babel/preset-env@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.0.tgz#c55db400c515a303662faaefd2d87e796efa08d0" + integrity sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w== dependencies: - "@babel/compat-data" "^7.28.6" + "@babel/compat-data" "^7.29.0" "@babel/helper-compilation-targets" "^7.28.6" "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" @@ -990,7 +1022,7 @@ "@babel/plugin-syntax-import-attributes" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.6" + "@babel/plugin-transform-async-generator-functions" "^7.29.0" "@babel/plugin-transform-async-to-generator" "^7.28.6" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" "@babel/plugin-transform-block-scoping" "^7.28.6" @@ -1001,7 +1033,7 @@ "@babel/plugin-transform-destructuring" "^7.28.5" "@babel/plugin-transform-dotall-regex" "^7.28.6" "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.28.6" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-dynamic-import" "^7.27.1" "@babel/plugin-transform-explicit-resource-management" "^7.28.6" "@babel/plugin-transform-exponentiation-operator" "^7.28.6" @@ -1014,9 +1046,9 @@ "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" "@babel/plugin-transform-modules-commonjs" "^7.28.6" - "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-systemjs" "^7.29.0" "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-new-target" "^7.27.1" "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" "@babel/plugin-transform-numeric-separator" "^7.28.6" @@ -1028,7 +1060,7 @@ "@babel/plugin-transform-private-methods" "^7.28.6" "@babel/plugin-transform-private-property-in-object" "^7.28.6" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.6" + "@babel/plugin-transform-regenerator" "^7.29.0" "@babel/plugin-transform-regexp-modifiers" "^7.28.6" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" @@ -1041,10 +1073,10 @@ "@babel/plugin-transform-unicode-regex" "^7.27.1" "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" + babel-plugin-polyfill-corejs2 "^0.4.15" + babel-plugin-polyfill-corejs3 "^0.14.0" + babel-plugin-polyfill-regenerator "^0.6.6" + core-js-compat "^3.48.0" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1124,6 +1156,19 @@ "@babel/types" "^7.28.6" debug "^4.3.1" +"@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.4.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" @@ -1148,6 +1193,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" +"@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1175,6 +1228,227 @@ dependencies: tslib "^2.4.0" +"@esbuild/aix-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c" + integrity sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw== + +"@esbuild/android-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz#61ea550962d8aa12a9b33194394e007657a6df57" + integrity sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA== + +"@esbuild/android-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz#554887821e009dd6d853f972fde6c5143f1de142" + integrity sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA== + +"@esbuild/android-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz#a7ce9d0721825fc578f9292a76d9e53334480ba2" + integrity sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A== + +"@esbuild/darwin-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz#2cb7659bd5d109803c593cfc414450d5430c8256" + integrity sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg== + +"@esbuild/darwin-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz#e741fa6b1abb0cd0364126ba34ca17fd5e7bf509" + integrity sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA== + +"@esbuild/freebsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz#2b64e7116865ca172d4ce034114c21f3c93e397c" + integrity sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g== + +"@esbuild/freebsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz#e5252551e66f499e4934efb611812f3820e990bb" + integrity sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA== + +"@esbuild/linux-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz#dc4acf235531cd6984f5d6c3b13dbfb7ddb303cb" + integrity sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw== + +"@esbuild/linux-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz#56a900e39240d7d5d1d273bc053daa295c92e322" + integrity sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw== + +"@esbuild/linux-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz#d4a36d473360f6870efcd19d52bbfff59a2ed1cc" + integrity sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w== + +"@esbuild/linux-loong64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz#fcf0ab8c3eaaf45891d0195d4961cb18b579716a" + integrity sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg== + +"@esbuild/linux-mips64el@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz#598b67d34048bb7ee1901cb12e2a0a434c381c10" + integrity sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw== + +"@esbuild/linux-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz#3846c5df6b2016dab9bc95dde26c40f11e43b4c0" + integrity sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ== + +"@esbuild/linux-riscv64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz#173d4475b37c8d2c3e1707e068c174bb3f53d07d" + integrity sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA== + +"@esbuild/linux-s390x@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz#f7a4790105edcab8a5a31df26fbfac1aa3dacfab" + integrity sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w== + +"@esbuild/linux-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz#2ecc1284b1904aeb41e54c9ddc7fcd349b18f650" + integrity sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA== + +"@esbuild/netbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz#e2863c2cd1501845995cb11adf26f7fe4be527b0" + integrity sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw== + +"@esbuild/netbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz#93f7609e2885d1c0b5a1417885fba8d1fcc41272" + integrity sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA== + +"@esbuild/openbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz#a1985604a203cdc325fd47542e106fafd698f02e" + integrity sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA== + +"@esbuild/openbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz#8209e46c42f1ffbe6e4ef77a32e1f47d404ad42a" + integrity sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg== + +"@esbuild/openharmony-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz#8fade4441893d9cc44cbd7dcf3776f508ab6fb2f" + integrity sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag== + +"@esbuild/sunos-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz#980d4b9703a16f0f07016632424fc6d9a789dfc2" + integrity sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg== + +"@esbuild/win32-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz#1c09a3633c949ead3d808ba37276883e71f6111a" + integrity sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg== + +"@esbuild/win32-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz#1b1e3a63ad4bef82200fef4e369e0fff7009eee5" + integrity sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ== + +"@esbuild/win32-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz#9e585ab6086bef994c6e8a5b3a0481219ada862b" + integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ== + +"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.1": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== + dependencies: + "@eslint/object-schema" "^2.1.7" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== + dependencies: + "@eslint/core" "^0.17.0" + +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.1" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.39.2": + version "9.39.2" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" + integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== + +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== + +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== + dependencies: + "@eslint/core" "^0.17.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -1203,50 +1477,49 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.2.0.tgz#c52fcd5b58fdd2e8eb66b2fd8ae56f2f64d05b28" - integrity sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ== +"@jest/console@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.3.0.tgz#42ccc3f995d400a8fe35b8850cfe10a8d4804cdf" + integrity sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" - jest-message-util "30.2.0" - jest-util "30.2.0" + jest-message-util "30.3.0" + jest-util "30.3.0" slash "^3.0.0" -"@jest/core@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.2.0.tgz#813d59faa5abd5510964a8b3a7b17cc77b775275" - integrity sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ== +"@jest/core@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.3.0.tgz#d06bb8456f35350f6494fd2405bcec4abb97b994" + integrity sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw== dependencies: - "@jest/console" "30.2.0" + "@jest/console" "30.3.0" "@jest/pattern" "30.0.1" - "@jest/reporters" "30.2.0" - "@jest/test-result" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" + "@jest/reporters" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" ansi-escapes "^4.3.2" chalk "^4.1.2" ci-info "^4.2.0" exit-x "^0.2.2" graceful-fs "^4.2.11" - jest-changed-files "30.2.0" - jest-config "30.2.0" - jest-haste-map "30.2.0" - jest-message-util "30.2.0" + jest-changed-files "30.3.0" + jest-config "30.3.0" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" jest-regex-util "30.0.1" - jest-resolve "30.2.0" - jest-resolve-dependencies "30.2.0" - jest-runner "30.2.0" - jest-runtime "30.2.0" - jest-snapshot "30.2.0" - jest-util "30.2.0" - jest-validate "30.2.0" - jest-watcher "30.2.0" - micromatch "^4.0.8" - pretty-format "30.2.0" + jest-resolve "30.3.0" + jest-resolve-dependencies "30.3.0" + jest-runner "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + jest-watcher "30.3.0" + pretty-format "30.3.0" slash "^3.0.0" "@jest/diff-sequences@30.0.1": @@ -1254,15 +1527,20 @@ resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== -"@jest/environment@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.2.0.tgz#1e673cdb8b93ded707cf6631b8353011460831fa" - integrity sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g== +"@jest/diff-sequences@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz#25b0818d3d83f00b9c7b04e069b8810f9014b143" + integrity sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA== + +"@jest/environment@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.3.0.tgz#b0657c2944b6ef3352f7b25903cc3a23e6ab70f6" + integrity sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw== dependencies: - "@jest/fake-timers" "30.2.0" - "@jest/types" "30.2.0" + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-mock "30.2.0" + jest-mock "30.3.0" "@jest/expect-utils@30.1.2": version "30.1.2" @@ -1271,47 +1549,47 @@ dependencies: "@jest/get-type" "30.1.0" -"@jest/expect-utils@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" - integrity sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA== +"@jest/expect-utils@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.3.0.tgz#c45b2da9802ffed33bf43b3e019ddb95e5ad95e8" + integrity sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA== dependencies: "@jest/get-type" "30.1.0" -"@jest/expect@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.2.0.tgz#9a5968499bb8add2bbb09136f69f7df5ddbf3185" - integrity sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA== +"@jest/expect@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.3.0.tgz#08ee7f5b610167b0068743246c0b568f4c40c773" + integrity sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg== dependencies: - expect "30.2.0" - jest-snapshot "30.2.0" + expect "30.3.0" + jest-snapshot "30.3.0" -"@jest/fake-timers@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.2.0.tgz#0941ddc28a339b9819542495b5408622dc9e94ec" - integrity sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw== +"@jest/fake-timers@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.3.0.tgz#2b2868130c1d28233a79566874c42cae1c5a70bc" + integrity sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ== dependencies: - "@jest/types" "30.2.0" - "@sinonjs/fake-timers" "^13.0.0" + "@jest/types" "30.3.0" + "@sinonjs/fake-timers" "^15.0.0" "@types/node" "*" - jest-message-util "30.2.0" - jest-mock "30.2.0" - jest-util "30.2.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" "@jest/get-type@30.1.0": version "30.1.0" resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== -"@jest/globals@30.2.0", "@jest/globals@^30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.2.0.tgz#2f4b696d5862664b89c4ee2e49ae24d2bb7e0988" - integrity sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw== +"@jest/globals@30.3.0", "@jest/globals@^30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.3.0.tgz#40f4c90e5602629ecda1ca773a8fb21575bb64ea" + integrity sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA== dependencies: - "@jest/environment" "30.2.0" - "@jest/expect" "30.2.0" - "@jest/types" "30.2.0" - jest-mock "30.2.0" + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/types" "30.3.0" + jest-mock "30.3.0" "@jest/pattern@30.0.1": version "30.0.1" @@ -1321,31 +1599,31 @@ "@types/node" "*" jest-regex-util "30.0.1" -"@jest/reporters@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.2.0.tgz#a36b28fcbaf0c4595250b108e6f20e363348fd91" - integrity sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ== +"@jest/reporters@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.3.0.tgz#0c1065f6c892665e5a051df22b19df4466ed816b" + integrity sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "30.2.0" - "@jest/test-result" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" + "@jest/console" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@jridgewell/trace-mapping" "^0.3.25" "@types/node" "*" chalk "^4.1.2" collect-v8-coverage "^1.0.2" exit-x "^0.2.2" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" istanbul-lib-coverage "^3.0.0" istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^5.0.0" istanbul-reports "^3.1.3" - jest-message-util "30.2.0" - jest-util "30.2.0" - jest-worker "30.2.0" + jest-message-util "30.3.0" + jest-util "30.3.0" + jest-worker "30.3.0" slash "^3.0.0" string-length "^4.0.2" v8-to-istanbul "^9.0.1" @@ -1357,12 +1635,12 @@ dependencies: "@sinclair/typebox" "^0.34.0" -"@jest/snapshot-utils@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz#387858eb90c2f98f67bff327435a532ac5309fbe" - integrity sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug== +"@jest/snapshot-utils@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz#ca003c91a3e1e4e4956dee716a2aaf04b6707f31" + integrity sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" chalk "^4.1.2" graceful-fs "^4.2.11" natural-compare "^1.4.0" @@ -1376,43 +1654,42 @@ callsites "^3.1.0" graceful-fs "^4.2.11" -"@jest/test-result@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.2.0.tgz#9c0124377fb7996cdffb86eda3dbc56eacab363d" - integrity sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg== +"@jest/test-result@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.3.0.tgz#cd8882d683d467fcffb98c09501a65687a76aae9" + integrity sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ== dependencies: - "@jest/console" "30.2.0" - "@jest/types" "30.2.0" + "@jest/console" "30.3.0" + "@jest/types" "30.3.0" "@types/istanbul-lib-coverage" "^2.0.6" collect-v8-coverage "^1.0.2" -"@jest/test-sequencer@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz#bf0066bc72e176d58f5dfa7f212b6e7eee44f221" - integrity sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q== +"@jest/test-sequencer@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz#27002b2093f4e0d9e0e1ebb0bc274a242fdadc14" + integrity sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA== dependencies: - "@jest/test-result" "30.2.0" + "@jest/test-result" "30.3.0" graceful-fs "^4.2.11" - jest-haste-map "30.2.0" + jest-haste-map "30.3.0" slash "^3.0.0" -"@jest/transform@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.2.0.tgz#54bef1a4510dcbd58d5d4de4fe2980a63077ef2a" - integrity sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA== +"@jest/transform@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.3.0.tgz#9e6f78ffa205449bf956e269fd707c160f47ce2f" + integrity sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A== dependencies: "@babel/core" "^7.27.4" - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@jridgewell/trace-mapping" "^0.3.25" babel-plugin-istanbul "^7.0.1" chalk "^4.1.2" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.11" - jest-haste-map "30.2.0" + jest-haste-map "30.3.0" jest-regex-util "30.0.1" - jest-util "30.2.0" - micromatch "^4.0.8" + jest-util "30.3.0" pirates "^4.0.7" slash "^3.0.0" write-file-atomic "^5.0.1" @@ -1430,10 +1707,10 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" -"@jest/types@30.2.0": - version "30.2.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.2.0.tgz#1c678a7924b8f59eafd4c77d56b6d0ba976d62b8" - integrity sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg== +"@jest/types@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.3.0.tgz#cada800d323cb74945c24ac74615fdb312a6c85f" + integrity sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw== dependencies: "@jest/pattern" "30.0.1" "@jest/schemas" "30.0.5" @@ -1443,7 +1720,7 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== @@ -1464,7 +1741,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": version "1.5.5" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== @@ -1486,6 +1763,27 @@ "@emnapi/runtime" "^1.4.3" "@tybys/wasm-util" "^0.10.0" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -1496,6 +1794,116 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== +"@rollup/rollup-android-arm-eabi@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz#f3ff5dbde305c4fa994d49aeb0a5db5305eff03b" + integrity sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng== + +"@rollup/rollup-android-arm64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz#c97d6ee47846a7ab1cd38e968adce25444a90a19" + integrity sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw== + +"@rollup/rollup-darwin-arm64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz#a13fc2d82e01eaf8ac823634a3f5f76fd9d0f938" + integrity sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw== + +"@rollup/rollup-darwin-x64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz#db4fa8b2b76d86f7e9b68ce4661fafe9767adf9b" + integrity sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A== + +"@rollup/rollup-freebsd-arm64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz#b2c6039de4b75efd3f29417fcb1a795c75a4e3ee" + integrity sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA== + +"@rollup/rollup-freebsd-x64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz#9ae2a216c94f87912a596a3b3a2ec5199a689ba5" + integrity sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz#69d5de7f781132f138514f2b900c523e38e2461f" + integrity sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ== + +"@rollup/rollup-linux-arm-musleabihf@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz#b6431e5699747f285306ffe8c1194d7af74f801f" + integrity sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA== + +"@rollup/rollup-linux-arm64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz#a32931baec8a0fa7b3288afb72d400ae735112c2" + integrity sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng== + +"@rollup/rollup-linux-arm64-musl@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz#0ad72572b01eb946c0b1a7a6f17ab3be6689a963" + integrity sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg== + +"@rollup/rollup-linux-loong64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz#05681f000310906512279944b5bef38c0cd4d326" + integrity sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw== + +"@rollup/rollup-linux-ppc64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz#9847a8c9dd76d687c3bdbe38d7f5f32c6b2743c8" + integrity sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA== + +"@rollup/rollup-linux-riscv64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz#173f20c278ac770ae3e969663a27d172a4545e87" + integrity sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ== + +"@rollup/rollup-linux-riscv64-musl@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz#db70c2377ae1ef61ef8673354d107ecb3fa7ffed" + integrity sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A== + +"@rollup/rollup-linux-s390x-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz#b2c461778add1c2ee70ec07d1788611548647962" + integrity sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ== + +"@rollup/rollup-linux-x64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz#ab140b356569601f57ab8727bd7306463841894f" + integrity sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ== + +"@rollup/rollup-linux-x64-musl@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz#810134b4a9d0d88576938f2eed38999a653814a1" + integrity sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw== + +"@rollup/rollup-openharmony-arm64@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz#0182bae7a54e748be806acef7a7f726f6949213c" + integrity sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg== + +"@rollup/rollup-win32-arm64-msvc@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz#1f19349bd1c5e454d03e4508a9277b6354985b9d" + integrity sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw== + +"@rollup/rollup-win32-ia32-msvc@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz#234ff739993539f64efac6c2e59704a691a309c2" + integrity sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ== + +"@rollup/rollup-win32-x64-gnu@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz#a4df0507c3be09c152a795cfc0c4f0c225765c5c" + integrity sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ== + +"@rollup/rollup-win32-x64-msvc@4.54.0": + version "4.54.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz#beacb356412eef5dc0164e9edfee51c563732054" + integrity sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg== + "@sinclair/typebox@^0.34.0": version "0.34.41" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" @@ -1508,10 +1916,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^13.0.0": - version "13.0.5" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" - integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== +"@sinonjs/fake-timers@^15.0.0": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz#e1a6f7171941aadcc31d2cea1744264d58b8b34c" + integrity sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw== dependencies: "@sinonjs/commons" "^3.0.1" @@ -1555,6 +1963,16 @@ dependencies: "@babel/types" "^7.28.2" +"@types/emscripten@^1.41.5": + version "1.41.5" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.41.5.tgz#5670e4b52b098691cb844b84ee48c9176699b68d" + integrity sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q== + +"@types/estree@1.0.8", "@types/estree@^1.0.6": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -1582,6 +2000,11 @@ expect "^30.0.0" pretty-format "^30.0.0" +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/node@*": version "24.5.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.1.tgz#dab6917c47113eb4502d27d06e89a407ec0eff95" @@ -1589,6 +2012,13 @@ dependencies: undici-types "~7.12.0" +"@types/node@^25.0.3": + version "25.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.3.tgz#79b9ac8318f373fbfaaf6e2784893efa9701f269" + integrity sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA== + dependencies: + undici-types "~7.16.0" + "@types/stack-utils@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -1606,6 +2036,66 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/project-service@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.50.0.tgz#1422366b7cc11fef8c6d87770884e608093423a4" + integrity sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.50.0" + "@typescript-eslint/types" "^8.50.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz#e0d6c838dc9044bc679724611b138cb34c81bddf" + integrity sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A== + dependencies: + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" + +"@typescript-eslint/tsconfig-utils@8.50.0", "@typescript-eslint/tsconfig-utils@^8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz#5c17537ad4c8a13bf6d7393035edaf91a1e13191" + integrity sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w== + +"@typescript-eslint/types@8.50.0", "@typescript-eslint/types@^8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.50.0.tgz#ad8f1ad88ae0096f548c9cdf60da9b92832db96e" + integrity sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w== + +"@typescript-eslint/typescript-estree@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz#2871d36617f81a127db905fa91b16d1a0251411b" + integrity sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ== + dependencies: + "@typescript-eslint/project-service" "8.50.0" + "@typescript-eslint/tsconfig-utils" "8.50.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" + debug "^4.3.4" + minimatch "^9.0.4" + semver "^7.6.0" + tinyglobby "^0.2.15" + ts-api-utils "^2.1.0" + +"@typescript-eslint/utils@^8.0.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.50.0.tgz#107f20a5747eab5db988c5f6ad462b59851cdd1f" + integrity sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg== + dependencies: + "@eslint-community/eslint-utils" "^4.7.0" + "@typescript-eslint/scope-manager" "8.50.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/typescript-estree" "8.50.0" + +"@typescript-eslint/visitor-keys@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz#79d1c95474e08f844dbe13370715cfb9b7e21363" + integrity sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q== + dependencies: + "@typescript-eslint/types" "8.50.0" + eslint-visitor-keys "^4.2.1" + "@ungap/structured-clone@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" @@ -1708,6 +2198,26 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1742,6 +2252,11 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1757,15 +2272,30 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -babel-jest@30.2.0, babel-jest@^30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.2.0.tgz#fd44a1ec9552be35ead881f7381faa7d8f3b95ac" - integrity sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +async@^3.2.4: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +babel-jest@30.3.0, babel-jest@^30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.3.0.tgz#3ff5553fa3bcbb8738d2d7335a4dbdc3bd1a0eb5" + integrity sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ== dependencies: - "@jest/transform" "30.2.0" + "@jest/transform" "30.3.0" "@types/babel__core" "^7.20.5" babel-plugin-istanbul "^7.0.1" - babel-preset-jest "30.2.0" + babel-preset-jest "30.3.0" chalk "^4.1.2" graceful-fs "^4.2.11" slash "^3.0.0" @@ -1781,36 +2311,36 @@ babel-plugin-istanbul@^7.0.1: istanbul-lib-instrument "^6.0.2" test-exclude "^6.0.0" -babel-plugin-jest-hoist@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz#94c250d36b43f95900f3a219241e0f4648191ce2" - integrity sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA== +babel-plugin-jest-hoist@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz#235ad714a45c18b12566becf439e1c604e277015" + integrity sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg== dependencies: "@types/babel__core" "^7.20.5" -babel-plugin-polyfill-corejs2@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" - integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== +babel-plugin-polyfill-corejs2@^0.4.15: + version "0.4.16" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.16.tgz#a1321145f6cde738b0a412616b6bcf77f143ab36" + integrity sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw== dependencies: - "@babel/compat-data" "^7.27.7" - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.7" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" - integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== +babel-plugin-polyfill-corejs3@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.1.tgz#75fb533a1c23c0a976f189cba1d035199705b8ad" + integrity sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" - core-js-compat "^3.43.0" + "@babel/helper-define-polyfill-provider" "^0.6.7" + core-js-compat "^3.48.0" -babel-plugin-polyfill-regenerator@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" - integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== +babel-plugin-polyfill-regenerator@^0.6.6: + version "0.6.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.7.tgz#eca723d67ef87b798881ad00546db1b6dd72e1ef" + integrity sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/helper-define-polyfill-provider" "^0.6.7" babel-preset-current-node-syntax@^1.2.0: version "1.2.0" @@ -1833,12 +2363,12 @@ babel-preset-current-node-syntax@^1.2.0: "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" -babel-preset-jest@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz#04717843e561347781d6d7f69c81e6bcc3ed11ce" - integrity sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ== +babel-preset-jest@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz#21cf3d19a6f5e9924426c879ee0b7f092636d043" + integrity sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ== dependencies: - babel-plugin-jest-hoist "30.2.0" + babel-plugin-jest-hoist "30.3.0" babel-preset-current-node-syntax "^1.2.0" balanced-match@^1.0.0: @@ -1851,6 +2381,11 @@ baseline-browser-mapping@^2.8.3: resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz#e553e12272c4965682743705efd8b4b4cf0d709b" integrity sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw== +baseline-browser-mapping@^2.9.0: + version "2.10.8" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz#23d1cea1a85b181c2b8660b6cfe626dc2fb15630" + integrity sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ== + brace-expansion@^1.1.7: version "1.1.12" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" @@ -1873,7 +2408,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.25.3: +browserslist@^4.24.0: version "4.26.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.2.tgz#7db3b3577ec97f1140a52db4936654911078cef3" integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== @@ -1884,6 +2419,17 @@ browserslist@^4.24.0, browserslist@^4.25.3: node-releases "^2.0.21" update-browserslist-db "^1.1.3" +browserslist@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + bs-logger@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" @@ -1903,7 +2449,19 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -callsites@^3.1.0: +bundle-require@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" + integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== + dependencies: + load-tsconfig "^0.2.3" + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== @@ -1923,7 +2481,12 @@ caniuse-lite@^1.0.30001741: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== -chalk@^4.1.2: +caniuse-lite@^1.0.30001759: + version "1.0.30001779" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz#75e4941d406928ba00c8d7a3ddda0b2cb90d7474" + integrity sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA== + +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1936,6 +2499,13 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + ci-info@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" @@ -1977,22 +2547,47 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^13.0.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +consola@^3.4.0: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-js-compat@^3.43.0: - version "3.45.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.45.1.tgz#424f3f4af30bf676fd1b67a579465104f64e9c7a" - integrity sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA== +core-js-compat@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" + integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== dependencies: - browserslist "^4.25.3" + browserslist "^4.28.1" cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" @@ -2003,7 +2598,7 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.4.1: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -2015,6 +2610,11 @@ dedent@^1.6.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -2025,6 +2625,13 @@ detect-newline@^3.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2035,6 +2642,16 @@ electron-to-chromium@^1.5.218: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.220.tgz#a9853fa5edcf51f4c7db369144377cf31d783b8f" integrity sha512-TWXijEwR1ggr4BdAKrb1nMNqYLTx1/4aD1fkeZU+FVJGTKu53/T7UyHKXlqEX3Ub02csyHePbHmkvnrjcaYzMA== +electron-to-chromium@^1.5.263: + version "1.5.313" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz#193e9ae2c2ab6915acb41e833068381e4ef0b3e4" + integrity sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA== + +email-addresses@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" + integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -2057,21 +2674,161 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +esbuild@^0.27.0: + version "0.27.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.2.tgz#d83ed2154d5813a5367376bb2292a9296fc83717" + integrity sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.2" + "@esbuild/android-arm" "0.27.2" + "@esbuild/android-arm64" "0.27.2" + "@esbuild/android-x64" "0.27.2" + "@esbuild/darwin-arm64" "0.27.2" + "@esbuild/darwin-x64" "0.27.2" + "@esbuild/freebsd-arm64" "0.27.2" + "@esbuild/freebsd-x64" "0.27.2" + "@esbuild/linux-arm" "0.27.2" + "@esbuild/linux-arm64" "0.27.2" + "@esbuild/linux-ia32" "0.27.2" + "@esbuild/linux-loong64" "0.27.2" + "@esbuild/linux-mips64el" "0.27.2" + "@esbuild/linux-ppc64" "0.27.2" + "@esbuild/linux-riscv64" "0.27.2" + "@esbuild/linux-s390x" "0.27.2" + "@esbuild/linux-x64" "0.27.2" + "@esbuild/netbsd-arm64" "0.27.2" + "@esbuild/netbsd-x64" "0.27.2" + "@esbuild/openbsd-arm64" "0.27.2" + "@esbuild/openbsd-x64" "0.27.2" + "@esbuild/openharmony-arm64" "0.27.2" + "@esbuild/sunos-x64" "0.27.2" + "@esbuild/win32-arm64" "0.27.2" + "@esbuild/win32-ia32" "0.27.2" + "@esbuild/win32-x64" "0.27.2" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^10.1.8: + version "10.1.8" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" + integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== + +eslint-plugin-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.5.0.tgz#f2d8e52ff1c023a69c22192838faccdf7adfdfe9" + integrity sha512-DAi9H8xN/TUuNOt+xDP1RqpCJLsSxBb5u1zXSpCyp0VAWGL8MBAg5t7/Dk+76iX7d1LhWu4DDH77IQNUolLDyg== + dependencies: + "@typescript-eslint/utils" "^8.0.0" + +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.39.2: + version "9.39.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" + integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.39.2" + "@eslint/plugin-kit" "^0.4.1" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2097,17 +2854,17 @@ exit-x@^0.2.2: resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== -expect@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" - integrity sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw== +expect@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.3.0.tgz#1b82111517d1ab030f3db0cf1b4061c8aa644f61" + integrity sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q== dependencies: - "@jest/expect-utils" "30.2.0" + "@jest/expect-utils" "30.3.0" "@jest/get-type" "30.1.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-mock "30.2.0" - jest-util "30.2.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" expect@^30.0.0: version "30.1.2" @@ -2121,11 +2878,39 @@ expect@^30.0.0: jest-mock "30.0.5" jest-util "30.0.5" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + dependencies: + reusify "^1.0.4" + fb-watchman@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -2133,6 +2918,32 @@ fb-watchman@^2.0.2: dependencies: bser "2.1.1" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +filename-reserved-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" + integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== + +filenamify@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== + dependencies: + filename-reserved-regex "^2.0.0" + strip-outer "^1.0.1" + trim-repeated "^1.0.0" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -2140,6 +2951,15 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2148,6 +2968,36 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +fix-dts-default-cjs-exports@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz#955cb6b3d519691c57828b078adadf2cb92e9549" + integrity sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg== + dependencies: + magic-string "^0.30.17" + mlly "^1.7.4" + rollup "^4.34.8" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + foreground-child@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" @@ -2156,12 +3006,21 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" +fs-extra@^11.1.1: + version "11.3.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.3.tgz#a27da23b72524e81ac6c3815cc0179b8c74c59ee" + integrity sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.3: +fsevents@^2.3.3, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -2191,10 +3050,37 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== +gh-pages@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-6.3.0.tgz#a5b9476dd4385ceaf85c6467b2e05397093e7613" + integrity sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA== + dependencies: + async "^3.2.4" + commander "^13.0.0" + email-addresses "^5.0.0" + filenamify "^4.3.0" + find-cache-dir "^3.3.1" + fs-extra "^11.1.1" + globby "^11.1.0" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -2215,7 +3101,24 @@ glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.2.11: +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -2254,6 +3157,19 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -2285,13 +3201,18 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: hasown "^2.0.2" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -2302,6 +3223,13 @@ is-generator-fn@^2.1.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -2368,84 +3296,83 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jest-changed-files@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.2.0.tgz#602266e478ed554e1e1469944faa7efd37cee61c" - integrity sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ== +jest-changed-files@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.3.0.tgz#055849df695f9a9fcde0ae44024f815bbc627f3a" + integrity sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA== dependencies: execa "^5.1.1" - jest-util "30.2.0" + jest-util "30.3.0" p-limit "^3.1.0" -jest-circus@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.2.0.tgz#98b8198b958748a2f322354311023d1d02e7603f" - integrity sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg== +jest-circus@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.3.0.tgz#153614c11ab35867f371bd93496ecb9690b92077" + integrity sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA== dependencies: - "@jest/environment" "30.2.0" - "@jest/expect" "30.2.0" - "@jest/test-result" "30.2.0" - "@jest/types" "30.2.0" + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" co "^4.6.0" dedent "^1.6.0" is-generator-fn "^2.1.0" - jest-each "30.2.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-runtime "30.2.0" - jest-snapshot "30.2.0" - jest-util "30.2.0" + jest-each "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" p-limit "^3.1.0" - pretty-format "30.2.0" + pretty-format "30.3.0" pure-rand "^7.0.0" slash "^3.0.0" stack-utils "^2.0.6" -jest-cli@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.2.0.tgz#1780f8e9d66bf84a10b369aea60aeda7697dcc67" - integrity sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA== +jest-cli@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.3.0.tgz#5ed75a337f486a1f1c5acbb2de8acddb106ead6c" + integrity sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw== dependencies: - "@jest/core" "30.2.0" - "@jest/test-result" "30.2.0" - "@jest/types" "30.2.0" + "@jest/core" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" chalk "^4.1.2" exit-x "^0.2.2" import-local "^3.2.0" - jest-config "30.2.0" - jest-util "30.2.0" - jest-validate "30.2.0" + jest-config "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" yargs "^17.7.2" -jest-config@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.2.0.tgz#29df8c50e2ad801cc59c406b50176c18c362a90b" - integrity sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA== +jest-config@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.3.0.tgz#b969e0aaaf5964419e62953bb712c16d15972425" + integrity sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w== dependencies: "@babel/core" "^7.27.4" "@jest/get-type" "30.1.0" "@jest/pattern" "30.0.1" - "@jest/test-sequencer" "30.2.0" - "@jest/types" "30.2.0" - babel-jest "30.2.0" + "@jest/test-sequencer" "30.3.0" + "@jest/types" "30.3.0" + babel-jest "30.3.0" chalk "^4.1.2" ci-info "^4.2.0" deepmerge "^4.3.1" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" - jest-circus "30.2.0" + jest-circus "30.3.0" jest-docblock "30.2.0" - jest-environment-node "30.2.0" + jest-environment-node "30.3.0" jest-regex-util "30.0.1" - jest-resolve "30.2.0" - jest-runner "30.2.0" - jest-util "30.2.0" - jest-validate "30.2.0" - micromatch "^4.0.8" + jest-resolve "30.3.0" + jest-runner "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" parse-json "^5.2.0" - pretty-format "30.2.0" + pretty-format "30.3.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -2459,15 +3386,15 @@ jest-diff@30.1.2: chalk "^4.1.2" pretty-format "30.0.5" -jest-diff@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" - integrity sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A== +jest-diff@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.3.0.tgz#e0a4c84ef350ffd790ffd5b0016acabeecf5f759" + integrity sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ== dependencies: - "@jest/diff-sequences" "30.0.1" + "@jest/diff-sequences" "30.3.0" "@jest/get-type" "30.1.0" chalk "^4.1.2" - pretty-format "30.2.0" + pretty-format "30.3.0" jest-docblock@30.2.0: version "30.2.0" @@ -2476,55 +3403,55 @@ jest-docblock@30.2.0: dependencies: detect-newline "^3.1.0" -jest-each@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.2.0.tgz#39e623ae71641c2ac3ee69b3ba3d258fce8e768d" - integrity sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ== +jest-each@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.3.0.tgz#faa7229bf7a9fa6426dc604057a7d2a173493b1e" + integrity sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA== dependencies: "@jest/get-type" "30.1.0" - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" chalk "^4.1.2" - jest-util "30.2.0" - pretty-format "30.2.0" + jest-util "30.3.0" + pretty-format "30.3.0" -jest-environment-node@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.2.0.tgz#3def7980ebd2fd86e74efd4d2e681f55ab38da0f" - integrity sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA== +jest-environment-node@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.3.0.tgz#aa8a57c5d0c4af0f8b1f7403ba737fec6b3aabbe" + integrity sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ== dependencies: - "@jest/environment" "30.2.0" - "@jest/fake-timers" "30.2.0" - "@jest/types" "30.2.0" + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-mock "30.2.0" - jest-util "30.2.0" - jest-validate "30.2.0" + jest-mock "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" -jest-haste-map@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.2.0.tgz#808e3889f288603ac70ff0ac047598345a66022e" - integrity sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw== +jest-haste-map@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.3.0.tgz#1ea6843e6e45c077d91270666a4fcba958c24cd5" + integrity sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@types/node" "*" anymatch "^3.1.3" fb-watchman "^2.0.2" graceful-fs "^4.2.11" jest-regex-util "30.0.1" - jest-util "30.2.0" - jest-worker "30.2.0" - micromatch "^4.0.8" + jest-util "30.3.0" + jest-worker "30.3.0" + picomatch "^4.0.3" walker "^1.0.8" optionalDependencies: fsevents "^2.3.3" -jest-leak-detector@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz#292fdca7b7c9cf594e1e570ace140b01d8beb736" - integrity sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ== +jest-leak-detector@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz#a695a851e353f517a554a2f5c91c2742fc131c98" + integrity sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ== dependencies: "@jest/get-type" "30.1.0" - pretty-format "30.2.0" + pretty-format "30.3.0" jest-matcher-utils@30.1.2: version "30.1.2" @@ -2536,15 +3463,15 @@ jest-matcher-utils@30.1.2: jest-diff "30.1.2" pretty-format "30.0.5" -jest-matcher-utils@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783" - integrity sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg== +jest-matcher-utils@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz#d6c739fec1ecd33809f2d2b1348f6ab01d2f2493" + integrity sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA== dependencies: "@jest/get-type" "30.1.0" chalk "^4.1.2" - jest-diff "30.2.0" - pretty-format "30.2.0" + jest-diff "30.3.0" + pretty-format "30.3.0" jest-message-util@30.1.0: version "30.1.0" @@ -2561,18 +3488,18 @@ jest-message-util@30.1.0: slash "^3.0.0" stack-utils "^2.0.6" -jest-message-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" - integrity sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw== +jest-message-util@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.3.0.tgz#4d723544d36890ba862ac3961db52db5b0d1ba39" + integrity sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw== dependencies: "@babel/code-frame" "^7.27.1" - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@types/stack-utils" "^2.0.3" chalk "^4.1.2" graceful-fs "^4.2.11" - micromatch "^4.0.8" - pretty-format "30.2.0" + picomatch "^4.0.3" + pretty-format "30.3.0" slash "^3.0.0" stack-utils "^2.0.6" @@ -2585,14 +3512,14 @@ jest-mock@30.0.5: "@types/node" "*" jest-util "30.0.5" -jest-mock@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" - integrity sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw== +jest-mock@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.3.0.tgz#e0fa4184a596a6c4fdec53d4f412158418923747" + integrity sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-util "30.2.0" + jest-util "30.3.0" jest-pnp-resolver@^1.2.3: version "1.2.3" @@ -2604,108 +3531,108 @@ jest-regex-util@30.0.1: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== -jest-resolve-dependencies@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz#3370e2c0b49cc560f6a7e8ec3a59dd99525e1a55" - integrity sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w== +jest-resolve-dependencies@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz#4d638c9f0d93a62a6ed25dec874bfd7e756c8ce5" + integrity sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw== dependencies: jest-regex-util "30.0.1" - jest-snapshot "30.2.0" + jest-snapshot "30.3.0" -jest-resolve@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.2.0.tgz#2e2009cbd61e8f1f003355d5ec87225412cebcd7" - integrity sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A== +jest-resolve@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.3.0.tgz#b7bee9927279805b1b50715d2170a545553b87ff" + integrity sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g== dependencies: chalk "^4.1.2" graceful-fs "^4.2.11" - jest-haste-map "30.2.0" + jest-haste-map "30.3.0" jest-pnp-resolver "^1.2.3" - jest-util "30.2.0" - jest-validate "30.2.0" + jest-util "30.3.0" + jest-validate "30.3.0" slash "^3.0.0" unrs-resolver "^1.7.11" -jest-runner@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.2.0.tgz#c62b4c3130afa661789705e13a07bdbcec26a114" - integrity sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ== - dependencies: - "@jest/console" "30.2.0" - "@jest/environment" "30.2.0" - "@jest/test-result" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" +jest-runner@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.3.0.tgz#fa970fc4e45d418ad7e7d581b24cac7af5944cb7" + integrity sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw== + dependencies: + "@jest/console" "30.3.0" + "@jest/environment" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" emittery "^0.13.1" exit-x "^0.2.2" graceful-fs "^4.2.11" jest-docblock "30.2.0" - jest-environment-node "30.2.0" - jest-haste-map "30.2.0" - jest-leak-detector "30.2.0" - jest-message-util "30.2.0" - jest-resolve "30.2.0" - jest-runtime "30.2.0" - jest-util "30.2.0" - jest-watcher "30.2.0" - jest-worker "30.2.0" + jest-environment-node "30.3.0" + jest-haste-map "30.3.0" + jest-leak-detector "30.3.0" + jest-message-util "30.3.0" + jest-resolve "30.3.0" + jest-runtime "30.3.0" + jest-util "30.3.0" + jest-watcher "30.3.0" + jest-worker "30.3.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.2.0.tgz#395ea792cde048db1b0cd1a92dc9cb9f1921bf8a" - integrity sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg== +jest-runtime@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.3.0.tgz#1a9bec7a9b68db12dfe4136bbe41ab883ea2c996" + integrity sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng== dependencies: - "@jest/environment" "30.2.0" - "@jest/fake-timers" "30.2.0" - "@jest/globals" "30.2.0" + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/globals" "30.3.0" "@jest/source-map" "30.0.1" - "@jest/test-result" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" cjs-module-lexer "^2.1.0" collect-v8-coverage "^1.0.2" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" - jest-haste-map "30.2.0" - jest-message-util "30.2.0" - jest-mock "30.2.0" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" jest-regex-util "30.0.1" - jest-resolve "30.2.0" - jest-snapshot "30.2.0" - jest-util "30.2.0" + jest-resolve "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.2.0.tgz#266fbbb4b95fc4665ce6f32f1f38eeb39f4e26d0" - integrity sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA== +jest-snapshot@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.3.0.tgz#6e7ea75069dda86e36311a0f73189e830d4f51ad" + integrity sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ== dependencies: "@babel/core" "^7.27.4" "@babel/generator" "^7.27.5" "@babel/plugin-syntax-jsx" "^7.27.1" "@babel/plugin-syntax-typescript" "^7.27.1" "@babel/types" "^7.27.3" - "@jest/expect-utils" "30.2.0" + "@jest/expect-utils" "30.3.0" "@jest/get-type" "30.1.0" - "@jest/snapshot-utils" "30.2.0" - "@jest/transform" "30.2.0" - "@jest/types" "30.2.0" + "@jest/snapshot-utils" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" babel-preset-current-node-syntax "^1.2.0" chalk "^4.1.2" - expect "30.2.0" + expect "30.3.0" graceful-fs "^4.2.11" - jest-diff "30.2.0" - jest-matcher-utils "30.2.0" - jest-message-util "30.2.0" - jest-util "30.2.0" - pretty-format "30.2.0" + jest-diff "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-util "30.3.0" + pretty-format "30.3.0" semver "^7.7.2" synckit "^0.11.8" @@ -2721,64 +3648,69 @@ jest-util@30.0.5: graceful-fs "^4.2.11" picomatch "^4.0.2" -jest-util@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" - integrity sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA== +jest-util@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.3.0.tgz#95a4fbacf2dac20e768e2f1744b70519f2ba7980" + integrity sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg== dependencies: - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" ci-info "^4.2.0" graceful-fs "^4.2.11" - picomatch "^4.0.2" + picomatch "^4.0.3" -jest-validate@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.2.0.tgz#273eaaed4c0963b934b5b31e96289edda6e0a2ef" - integrity sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw== +jest-validate@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.3.0.tgz#215e11b8fcc5e2ca4b99ea5d730a5b4c969e4355" + integrity sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q== dependencies: "@jest/get-type" "30.1.0" - "@jest/types" "30.2.0" + "@jest/types" "30.3.0" camelcase "^6.3.0" chalk "^4.1.2" leven "^3.1.0" - pretty-format "30.2.0" + pretty-format "30.3.0" -jest-watcher@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.2.0.tgz#f9c055de48e18c979e7756a3917e596e2d69b07b" - integrity sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg== +jest-watcher@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.3.0.tgz#3afa1af355b9fe80f0261eb8a23981a315858596" + integrity sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w== dependencies: - "@jest/test-result" "30.2.0" - "@jest/types" "30.2.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" ansi-escapes "^4.3.2" chalk "^4.1.2" emittery "^0.13.1" - jest-util "30.2.0" + jest-util "30.3.0" string-length "^4.0.2" -jest-worker@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.2.0.tgz#fd5c2a36ff6058ec8f74366ec89538cc99539d26" - integrity sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g== +jest-worker@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.3.0.tgz#ae4dc1f1d93d0cba1415624fcedaec40ea764f14" + integrity sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ== dependencies: "@types/node" "*" "@ungap/structured-clone" "^1.3.0" - jest-util "30.2.0" + jest-util "30.3.0" merge-stream "^2.0.0" supports-color "^8.1.1" -jest@^30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-30.2.0.tgz#9f0a71e734af968f26952b5ae4b724af82681630" - integrity sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A== +jest@^30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.3.0.tgz#6460b889dd805e9677400505f16f1d9b14c285a3" + integrity sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg== dependencies: - "@jest/core" "30.2.0" - "@jest/types" "30.2.0" + "@jest/core" "30.3.0" + "@jest/types" "30.3.0" import-local "^3.2.0" - jest-cli "30.2.0" + jest-cli "30.3.0" + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== js-tokens@^4.0.0: version "4.0.0" @@ -2793,6 +3725,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + jsesc@^3.0.2, jsesc@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -2803,26 +3742,75 @@ jsesc@~3.0.2: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +load-tsconfig@^0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" + integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2830,6 +3818,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -2840,6 +3835,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -2852,6 +3852,20 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -2876,6 +3890,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" @@ -2889,7 +3908,7 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2913,11 +3932,30 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +mlly@^1.7.4: + version "1.8.0" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" + integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + dependencies: + acorn "^8.15.0" + pathe "^2.0.3" + pkg-types "^1.3.1" + ufo "^1.6.1" + ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + napi-postinstall@^0.3.0: version "0.3.3" resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.3.tgz#93d045c6b576803ead126711d3093995198c6eb9" @@ -2943,6 +3981,11 @@ node-releases@^2.0.21: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== +node-releases@^2.0.27: + version "2.0.36" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" + integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -2955,6 +3998,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2969,6 +4017,18 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -2976,7 +4036,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -2990,6 +4050,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -3000,6 +4067,13 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -3038,6 +4112,16 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -3048,27 +4132,48 @@ picomatch@^2.0.4, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: +picomatch@^4.0.2, picomatch@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== -pirates@^4.0.7: +pirates@^4.0.1, pirates@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== -pkg-dir@^4.2.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -prettier@^3.7.4: - version "3.7.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f" - integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA== +pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +postcss-load-config@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== + dependencies: + lilconfig "^3.1.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== pretty-format@30.0.5, pretty-format@^30.0.0: version "30.0.5" @@ -3079,25 +4184,40 @@ pretty-format@30.0.5, pretty-format@^30.0.0: ansi-styles "^5.2.0" react-is "^18.3.1" -pretty-format@30.2.0: - version "30.2.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.2.0.tgz#2d44fe6134529aed18506f6d11509d8a62775ebe" - integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA== +pretty-format@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.3.0.tgz#e977eed4bcd1b6195faed418af8eac68b9ea1f29" + integrity sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ== dependencies: "@jest/schemas" "30.0.5" ansi-styles "^5.2.0" react-is "^18.3.1" +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pure-rand@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + regenerate-unicode-properties@^10.2.2: version "10.2.2" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" @@ -3165,21 +4285,69 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.22.10: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== +resolve@^1.22.11: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -semver@^6.3.1: +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rollup@^4.34.8: + version "4.54.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.54.0.tgz#930f4dfc41ff94d720006f9f62503612a6c319b8" + integrity sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.54.0" + "@rollup/rollup-android-arm64" "4.54.0" + "@rollup/rollup-darwin-arm64" "4.54.0" + "@rollup/rollup-darwin-x64" "4.54.0" + "@rollup/rollup-freebsd-arm64" "4.54.0" + "@rollup/rollup-freebsd-x64" "4.54.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.54.0" + "@rollup/rollup-linux-arm-musleabihf" "4.54.0" + "@rollup/rollup-linux-arm64-gnu" "4.54.0" + "@rollup/rollup-linux-arm64-musl" "4.54.0" + "@rollup/rollup-linux-loong64-gnu" "4.54.0" + "@rollup/rollup-linux-ppc64-gnu" "4.54.0" + "@rollup/rollup-linux-riscv64-gnu" "4.54.0" + "@rollup/rollup-linux-riscv64-musl" "4.54.0" + "@rollup/rollup-linux-s390x-gnu" "4.54.0" + "@rollup/rollup-linux-x64-gnu" "4.54.0" + "@rollup/rollup-linux-x64-musl" "4.54.0" + "@rollup/rollup-openharmony-arm64" "4.54.0" + "@rollup/rollup-win32-arm64-msvc" "4.54.0" + "@rollup/rollup-win32-ia32-msvc" "4.54.0" + "@rollup/rollup-win32-x64-gnu" "4.54.0" + "@rollup/rollup-win32-x64-msvc" "4.54.0" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^6.0.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -3189,7 +4357,7 @@ semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== -semver@^7.7.3: +semver@^7.6.0, semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== @@ -3234,6 +4402,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3317,6 +4490,26 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-outer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" + integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== + dependencies: + escape-string-regexp "^1.0.2" + +sucrase@^3.35.0: + version "3.35.1" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -3352,6 +4545,33 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.11, tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3364,6 +4584,28 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== + dependencies: + escape-string-regexp "^1.0.2" + +ts-api-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + ts-jest@^29.4.6: version "29.4.6" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9" @@ -3384,6 +4626,36 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsup@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.5.1.tgz#a9c7a875b93344bdf70600dedd78e70f88ec9a65" + integrity sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing== + dependencies: + bundle-require "^5.1.0" + cac "^6.7.14" + chokidar "^4.0.3" + consola "^3.4.0" + debug "^4.4.0" + esbuild "^0.27.0" + fix-dts-default-cjs-exports "^1.0.0" + joycon "^3.1.1" + picocolors "^1.1.1" + postcss-load-config "^6.0.1" + resolve-from "^5.0.0" + rollup "^4.34.8" + source-map "^0.7.6" + sucrase "^3.35.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.11" + tree-kill "^1.2.2" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -3404,6 +4676,11 @@ typescript@^5.9.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +ufo@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" + integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" @@ -3414,6 +4691,11 @@ undici-types@~7.12.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" @@ -3437,6 +4719,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unrs-resolver@^1.7.11: version "1.11.1" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" @@ -3472,6 +4759,21 @@ update-browserslist-db@^1.1.3: escalade "^3.2.0" picocolors "^1.1.1" +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + v8-to-istanbul@^9.0.1: version "9.3.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" @@ -3495,6 +4797,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"